Почему 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
", без упоминания какого-либо "порядка".
Ответ 2
==
является оператором двоичного равенства.
Похоже, что левый операнд бинарного оператора полностью вычисляется перед тем, как вычисляется любая часть правого операнда.
Спецификация Java 11> Порядок оценки> Оцените левый операнд первым
Ответ 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)