Почему замена целочисленной переменной XOR не работает в одной строке?

Я хочу поменять значение двух целых переменных в java с помощью оператора XOR.

Это мой код:

int i = 24;
int j = 17;

i ^= j;
j ^= i;
i ^= j;

System.out.println("i : " + i + "\t j : " + j);

Он будет работать нормально, но следующий эквивалентный код не работает:

int i = 24;
int j = 17;

i ^= j ^= i ^= j;

System.out.println("i : " + i + "\t j : " + j);

Вывод выглядит следующим образом:

i : 0    j : 24

Первая переменная равна нулю! Что не так с Java?

Ответ 1

Согласно спецификация Java (спецификация Java 7), раздел 15.26.2 (стр. 529).

Совокупное выражение выражения формы E1 op= E2 эквивалентно E1 = (T) ((E1) op (E2)), где T - тип E1, за исключением того, что E1 оценивается только один раз.

В соответствии с разделом 15.7 "Порядок оценки" (Страница 423) (emphasis):

15.7 Порядок оценки

Язык программирования Java гарантирует, что операнды операторов будут оцениваться в определенном порядке оценки, а именно слева направо.

15.7.1 Оценить первый операнд левой руки

Левый операнд двоичного оператора оказывается полностью оцененным до того, как будет оценена любая часть правого операнда.

Если оператор является оператором составного присваивания (§15.26.2), то оценка левого операнда включает в себя как запоминание переменной, которую левый операнд обозначает, так и выборку и сохранение этого значения переменной для использовать в подразумеваемой двоичной операции.

Если оценка левого операнда двоичного оператора завершается внезапно, никакая часть правого операнда, по-видимому, не была оценена.

Более подробно описано в разделе 15.26.2 (стр. 529):

Если выражение левого операнда не является выражением доступа к массиву, то:

• Во-первых, левый операнд оценивается для создания переменной. [Обрезается]

• В противном случае значение левого операнда сохраняется, а затем оценивается правый операнд. [Обрезается]

• В противном случае сохраненное значение левой переменной и значение правого операнда используются для выполнения двоичной операции, указанной оператором составного присваивания. [Обрезается]

• В противном случае результат двоичной операции преобразуется в тип левой переменной, подвергается преобразованию набора значений (п. 5.1.13) в соответствующее стандартное значение (не заданное значение расширенного показателя), и результат преобразования сохраняется в переменной.

Пример в документации

Пример 15.26.2-2. Значение левой части составного задания сохраняется до оценки правой стороны.

  class Test {
      public static void main(String[] args) {
          int k = 1;
          int[] a = { 1 };
          k += (k = 4) * (k + 2);
          a[0] += (a[0] = 4) * (a[0] + 2);
          System.out.println("k==" + k + " and a[0]==" + a[0]);
      }
  }

Итак, выражение в вопросе переписано и сгруппировано как:

i = i ^ (j = j ^ (i = i ^ j));

Левые операнды оцениваются:

i = 24 ^ (j = 17 ^ (i = 24 ^ 17));
    **

Так как значение i не обновляется, как ожидалось, это приведет к тому, что значение i получит 0, когда 24 будет заменено на j.

Ответ 2

Записывая своп все в одном утверждении, вы полагаетесь на побочные эффекты внутреннего выражения i ^= j относительно внешнего выражения i ^= (...).

Из спецификации Java (15.26 Assignment Operators):

Существует 12 операторов присваивания; все синтаксически право-ассоциативные (они группируются справа налево). Таким образом, a = b = c означает a = (b = c), который присваивает значение c в b, а затем присваивает значение от b до a.

[...]

AssignmentOperator: один из         = * =/=% = + = - = <= < = → = → >= & = ^ = | =

Вы можете рассмотреть возможность чтения кода. Возможно, это лучше всего, например, поместите код в метод подкачки() или выполните фактическую замену с помощью переменной temp:

int temp = i;
i = j;
j = temp;

Ответ 3

Левая часть i оценивается до ее изменения.

Вместо этого вы можете:

j ^= (i ^= j);
i ^= j;

Это немного менее компактно, но работает.

Ответ 4

Как сделать: i ^ = j ^ (j = j ^ я ^ j);

Ответ 5

Вот немного более короткое решение по сравнению с @nhahtdh. Я знаю, это старый вопрос, но просто хотел документировать его на Stackoverflow: P

i = i ^ j ^ (j = i)