Почему x == (x = y) не совпадает с (x = y) == x?

Рассмотрим следующий пример:

class Quirky {
    public static void main(String[] args) {
        int x = 1;
        int y = 3;

        System.out.println(x == (x = y)); // false
        x = 1; // reset
        System.out.println((x = y) == x); // true
     }
}

Я не уверен, есть ли в спецификации языка Java элемент, который диктует загрузку предыдущего значения переменной для сравнения с правой стороной (x = y), который в порядке, подразумеваемом скобками, должен быть вычислен первым.

Почему первое выражение оценивается как false, а второе - как true? Я ожидал, что (x = y) будет сначала оценен, а затем он сравнил бы x с самим собой (3) и вернул бы true.


Этот вопрос отличается от порядка вычисления подвыражений в выражении Java тем, что x здесь определенно не является "подвыражением". Его нужно загружать для сравнения, а не "оценивать". Вопрос специфичен для Java, и выражение x == (x = y), в отличие от надуманных непрактичных конструкций, обычно создаваемых для сложных вопросов интервью, пришло из реального проекта. Предполагалось, что это будет замена одной строки для сравнения и замены

int oldX = x;
x = y;
return oldX == y;

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

Ответы

Ответ 1

которые в порядке, указанном в скобках, должны быть рассчитаны в первую очередь

Нет. Это распространенное заблуждение, что круглые скобки оказывают (общее) влияние на порядок вычисления или оценки. Они только приводят части вашего выражения в определенное дерево, связывая нужные операнды с правильными операциями для задания.

(И, если вы не используете их, эта информация поступает из "приоритета" и ассоциативности операторов, что является результатом того, как определяется синтаксическое дерево языка. На самом деле, это все равно именно так, как оно работает, когда вы используйте скобки, но мы упрощаем и говорим, что тогда мы не полагаемся ни на какие правила приоритета.)

Как только это будет сделано (то есть, когда ваш код будет проанализирован в программе), эти операнды все еще должны быть оценены, и существуют отдельные правила о том, как это делается: упомянутые правила (как показал нам Эндрю) утверждают, что LHS каждой операции оценивается первым в Java.

Обратите внимание, что это не так на всех языках; например, в C++, если вы не используете оператор короткого замыкания, такой как && или || порядок вычисления операндов обычно не определен, и вы не должны полагаться на него в любом случае.

Учителя должны прекратить объяснять приоритет оператора, используя вводящие в заблуждение фразы, такие как "это делает сложение первым". Для выражения x * y + z правильным объяснением будет "приоритет оператора заставляет сложение происходить между x * y и z, а не между y и z ", без упоминания какого-либо "порядка".

Ответ 3

Как сказал Луис Вассерман, выражение оценивается слева направо. И java не заботится о том, что на самом деле делает "define", он заботится только о том, чтобы генерировать (энергонезависимое, окончательное) значение для работы.

//the example values
x = 1;
y = 3;

Итак, чтобы вычислить первый вывод System.out.println(), делается следующее:

x == (x = y)
1 == (x = y)
1 == (x = 3) //assign 3 to x, returns 3
1 == 3
false

и рассчитать второе:

(x = y) == x
(x = 3) == x //assign 3 to x, returns 3
3 == x
3 == 3
true

Обратите внимание, что второе значение всегда будет иметь значение true, независимо от начальных значений x и y, потому что вы эффективно сравниваете присвоение значения переменной, которой оно назначено, и a = b и b будут оцениваться в этом порядок, всегда будет одинаковым по определению.

Ответ 4

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

Есть. В следующий раз, когда вам непонятно, что говорится в спецификации, прочитайте ее, а затем задайте вопрос, если она неясна.

... правая часть (x = y) которая в порядке, указанном в скобках, должна быть рассчитана первой.

Это утверждение неверно. Скобки не подразумевают порядок оценки. В Java порядок вычисления слева направо, независимо от круглых скобок. Скобки определяют, где находятся границы подвыражения, а не порядок оценки.

Почему первое выражение оценивается как ложное, а второе - как истинное?

Правило для оператора ==: вычислить левую сторону для получения значения, вычислить правую сторону для получения значения, сравнить значения, сравнение - это значение выражения.

Другими словами, значение expr1 == expr2 всегда такое же, как если бы вы написали temp1 = expr1; temp2 = expr2; temp1 = expr1; temp2 = expr2; а затем оценили temp1 == temp2.

Правило для оператора = с локальной переменной слева: вычисление левой стороны для получения переменной, вычисление правой стороны для получения значения, выполнение присваивания, результатом является присвоенное значение.

Так что сложите это вместе:

x == (x = y)

У нас есть оператор сравнения. Оцените левую сторону, чтобы получить значение - мы получаем текущее значение x. Оцените правую сторону: это назначение, поэтому мы оцениваем левую сторону для получения переменной - переменной x - мы оцениваем правую сторону - текущее значение y - присваиваем ее x, и результатом является присвоенный значение. Затем мы сравниваем исходное значение x со значением, которое было присвоено.

Вы можете сделать (x = y) == x в качестве упражнения. Опять же, помните, что все правила оценки левой стороны предшествуют всем правилам оценки правой стороны.

Я ожидал, что (x = y) будет сначала оценен, а затем он сравнил бы x с самим собой (3) и вернул бы true.

Ваше ожидание основано на ряде неверных представлений о правилах Java. Надеюсь, теперь у вас есть правильные убеждения, и в будущем вы будете ожидать правды.

Этот вопрос отличается от "порядка вычисления подвыражений в выражении Java"

Это утверждение неверно. Этот вопрос совершенно уместен.

x здесь определенно не является "подвыражением".

Это утверждение также ложно. Это подвыражение дважды в каждом примере.

Его нужно загружать для сравнения, а не "оценивать".

Я без понятия что это значит.

Видимо у тебя еще много ложных убеждений. Мой совет, чтобы вы читали спецификацию, пока ваши ложные убеждения не будут заменены истинными убеждениями.

Вопрос специфичен для Java, и выражение x == (x = y), в отличие от надуманных непрактичных конструкций, обычно создаваемых для сложных вопросов интервью, пришло из реального проекта.

Происхождение выражения не имеет отношения к вопросу. Правила для таких выражений четко описаны в спецификации; прочитай это!

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

Поскольку эта однострочная замена вызывала у вас, читателя кода, большую путаницу, я бы сказал, что это был неудачный выбор. Делать код более кратким, но более сложным для понимания - не победа. Вряд ли код станет быстрее.

Кстати, в С# есть метод сравнения и замены в виде библиотечного метода, который можно сопоставить с машинной инструкцией. Я полагаю, что у Java нет такого метода, поскольку он не может быть представлен в системе типов Java.

Ответ 5

Это связано с приоритетом операторов и тем, как оцениваются операторы.

Скобки '()' имеют более высокий приоритет и имеют ассоциативность слева направо. Равенство '==' идет следующим в этом вопросе и имеет ассоциативность слева направо. Задание '=' идет последним и имеет ассоциативность справа налево.

Система использует стек для оценки выражения. Выражение оценивается слева направо.

Теперь приходит к оригинальному вопросу:

int x = 1;
int y = 3;
System.out.println(x == (x = y)); // false

Сначала x (1) будет помещен в стек. тогда внутренняя (x = y) будет оценена и помещена в стек со значением x (3). Теперь x (1) будет сравниваться с x (3), поэтому результат равен false.

x = 1; // reset
System.out.println((x = y) == x); // true

Здесь (x = y) будет оцениваться, теперь значение x станет 3, а x (3) будет помещено в стек. Теперь x (3) с измененным значением после равенства будет помещено в стек. Теперь выражение будет оценено, и оба будут одинаковыми, поэтому результат верен.

Ответ 6

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

С:

      x == (x = y)

Вы в основном делаете то же самое, что и:

      x == y

И х будет иметь значение у после сравнения.

Пока с:

      (x = y) == x

Вы в основном делаете то же самое, что и:

      x == x

После й взял у значения. И это всегда вернет истину.

Ответ 7

В первом тесте вы проверяете 1 == 3.

Во втором тесте ваша проверка делает 3 == 3.

(x = y) присваивает значение, и это значение проверяется. В первом примере сначала присваивается x = 1, затем присваивается x 3. 1 == 3?

В последнем случае х присваивается 3, и, очевидно, это все еще 3. 3 == 3?

Ответ 8

Рассмотрим другой, возможно, более простой пример:

int x = 1;
System.out.println(x == ++x); // false
x = 1; // reset
System.out.println(++x == x); // true

Здесь оператор предварительного увеличения в ++x должен быть применен до того, как будет выполнено сравнение - точно так же, как (x = y) в вашем примере должно быть вычислено до сравнения.

Тем не менее, оценка выражения по-прежнему происходит слева направо, поэтому первое сравнение на самом деле равно 1 == 2 а второе равно 2 == 2.
То же самое происходит в вашем примере.

Ответ 9

Выражения оцениваются слева направо. В этом случае:

int x = 1;
int y = 3;

x == (x = y)) // false
x ==    t

- left x = 1
- let t = (x = y) => x = 3
- x == (x = y)
  x == t
  1 == 3 //false

(x = y) == x); // true
   t    == x

- left (x = y) => x = 3
           t    =      3 
-  (x = y) == x
-     t    == x
-     3    == 3 //true

Ответ 10

По сути, первое утверждение x имеет значение 1, поэтому Java сравнивает 1 == с новой переменной x, которая не будет такой же

Во втором вы сказали x = y, что означает, что значение x изменилось, и поэтому, когда вы вызовете его снова, оно будет таким же, следовательно, почему оно истинно, а x == x

Ответ 11

Я использовал c++ для вашего кода в качестве оператора if, и они равны:

#include <iostream>
#include "conio.h"

using namespace std;

int main()
{
    int x = 1;
    int y = 3;


    if(x == (x = y) && (x = y) == x)
     cout<<"fine";
    else
    cout<<"wrong";

    getch();


    return 0;

}

Ответ 12

Большой Брю,

class Quirky {
public static void main(String[] args) {
    int x = 1;
    int y = 3;

    System.out.println(x == (x = y)); // (1 == (x=3)) 
    x = 1; // reset
    System.out.println((x = y) == x); // ((x=3) == 3)
 }

}

Как говорили все домашние парни, левая часть бинарного оператора равенства "==" всегда вычисляется первой! Во втором примере х присваивается 3, а затем сравнивается с его текущим я, что верно! В первом примере x оценивается, а затем переназначается на 3 с правой стороны.

Ответ 13

== является оператором равенства сравнения и работает слева направо.

x == (x = y);

здесь старое назначенное значение x сравнивается с новым назначенным значением x, (1 == 3)//false

(x = y) == x;

Принимая во внимание, что здесь новое присвоенное значение x сравнивается с новым удерживающим значением x, присвоенным ему непосредственно перед сравнением, (3 == 3)//true

Теперь рассмотрим это

    System.out.println((8 + (5 * 6)) * 9);
    System.out.println(8 + (5 * 6) * 9);
    System.out.println((8 + 5) * 6 * 9);
    System.out.println((8 + (5) * 6) * 9);
    System.out.println(8 + 5 * 6 * 9);

Выход:

342

278

702

342

278

Таким образом, круглые скобки играют важную роль в арифметических выражениях, а не в выражениях сравнения.

Ответ 14

Here most important thing to note is, expression inside (...) gets priority for execution and order of operation.

System.out.println(x == (x = y)); // false
(x == (x = y))
(1 == (1 = 3))
(1 == (3))
**False**

x = 1; // reset

System.out.println((x = y) == x); // true
((x = y) == x)
((1 = 3) == x)
((3) == x)
((3) == 3)
**True**

Ответ 15

Вопрос, который вы задали, является очень хорошим вопросом, если вы хотите написать компилятор Java или протестировать программы для проверки правильности работы компилятора Java. В Java эти два выражения должны давать результаты, которые вы видели. Например, в C++ им не нужно - так что если кто-то повторно использовал части компилятора C++ в своем компиляторе Java, вы можете теоретически обнаружить, что компилятор не ведет себя так, как должен.

Как разработчик программного обеспечения, пишущий код, который читабелен, понятен и удобен в обслуживании, обе версии вашего кода будут считаться ужасными. Чтобы понять, что делает код, нужно точно знать, как определяется язык Java. Кто-то, кто пишет код Java и C++, вздрогнул бы, глядя на код. Если вам нужно спросить, почему одна строка кода делает то, что делает, то вам следует избегать этого кода. (Я полагаю и надеюсь, что парни, которые правильно ответили на ваш вопрос "почему", сами также избежат этого кода).

Ответ 16

Легко во втором сравнении слева - присвоение после присвоения y x (слева), после чего вы сравниваете 3 == 3. В первом примере вы сравниваете x = 1 с новым назначением x = 3. Кажется, что всегда принимаются текущие состояния чтения операторов слева направо от х.

Ответ 17

Очень просто, когда вы проверяете в первый раз вашу проверку, чтобы видеть, является ли variable == true но во второй раз, когда вы проверяете, чтобы проверить, valid expression == variable ли valid expression == variable.

Учтите, что если x != null || 0 || false x != null || 0 || false x != null || 0 || false тогда это будет оценено как истина.

x == (x = y) - это не то же самое, что (x = y) == x потому что:

Первый просто спрашивает, if x == true
Второе спрашивает, if function.wasExecuted == true или if function.canExecute == true (возвращает true, если x может быть установлен в y) (Возвращает false, если x является const или по какой-либо другой причине не может быть установлено в y)
Другим примером выражения этого типа было бы, if (alert("hi") == true) это вернет true, если функция alert() может быть выполнена.

Пример: JSFiddle (да, я знаю, что вы просили Java, но эта логика переносится и на JavaScript)