考虑以下示例:
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
。
此问题与order of evaluation of subexpressions in a Java expression不同之处在于,x
绝对不是此处的“子表达式”。需要加载以进行比较,而不是对其进行“评估”。这个问题是特定于Java的,而表达式x == (x = y)
与通常为棘手的面试问题设计的牵强的不切实际的构造不同,它来自一个真实的项目。它原本应该是“一键替换”成语的单行替换
int oldX = x;
x = y;
return oldX == y;
比x86 CMPXCHG指令甚至更简单的指令,都应该在Java中使用较短的表达式。
答案 0 :(得分:161)
==
是二进制equality operator。
二元运算符的左操作数似乎已被完全评估,先于评估右操作数的任何部分。
Java 11 Specification > Evaluation Order > Evaluate Left-Hand Operand First
答案 1 :(得分:149)
正如LouisWasserman所说,该表达式是从左到右求值的。 Java并不关心“评估”的实际作用,它只关心生成一个(非易失性的,最终的)值以供使用。
//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
请注意,无论x
和y
的初始值如何,第二个值都将始终为true,因为您正在有效地将值的分配与分配给该变量的变量进行比较,并且a = b
和b
将按照该顺序求值,根据定义,它们始终是相同的。
答案 2 :(得分:96)
应按照括号中的顺序先计算
不。一个普遍的误解是括号对计算或评估顺序有任何(一般)影响。它们仅将表达式的各部分强制转换为特定的树,将正确的操作数绑定到作业的正确操作。
(而且,如果不使用它们,则此信息来自运算符的“优先级”和关联性,这是语言的语法树如何定义的结果。实际上,这仍然是它的确切用法。当您使用括号时可以使用,但是我们简化了一点,说我们当时不依赖任何优先级规则。)
一旦完成(即,一旦您的代码被解析成程序),那些操作数仍需要进行评估,并且关于如何完成操作有单独的规则:所述规则(如安德鲁向我们展示的那样)表明LHS首先用Java评估每个操作。
请注意,并非所有语言都如此。例如,在C ++中,除非您使用&&
或||
之类的短路运算符,否则通常不指定操作数的求值顺序,并且您不应以任何一种方式依赖它。>
教师需要停止使用误导性短语(例如“使加法首先发生”)来解释运算符的优先级。给定表达式x * y + z
,正确的解释是“运算符优先级使加法发生在x * y
和z
之间,而不是y
和z
之间”,其中没有提及任何“订单”。
答案 3 :(得分:25)
我不确定Java语言规范中是否有一项规定要加载变量的先前值...
有。下次您不清楚规格说明是什么时,请阅读规格说明,然后然后询问是否不清楚。
...右边的
(x = y)
(应按照方括号中的顺序计算)。
那句话是错误的。 括号并不意味着评估的顺序。在Java中,不管括号如何,评估顺序都是从左到右。括号确定子表达式边界的位置,而不是求值顺序。
为什么第一个表达式的计算结果为false,而第二个表达式的计算结果为true?
==
运算符的规则是:评估左侧产生一个值,评估右侧产生一个值,比较这些值,比较就是表达式的值。
换句话说,expr1 == 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)表达式不同于通常为棘手的面试问题而精心设计的牵强附会的结构,它来自一个真实项目。
表达式的来源与问题无关。在说明书中清楚地描述了这种表达的规则。读吧!
它应该是“一键替换”成语的单行替换
由于单行替换在您(代码的阅读者)中引起了很大的混乱,我建议这是一个糟糕的选择。使代码更简洁但更难理解并不是胜利。不太可能使代码更快。
顺便说一句,C#具有 compare and replace 作为一种库方法,可以将 简化为机器指令。我相信Java没有这种方法,因为它无法在Java类型系统中表示。
答案 4 :(得分:16)
它与运算符的优先级以及运算符的评估方式有关。
括号“()”具有较高的优先级,并且具有从左到右的关联性。 等式“ ==”紧随其后,从左到右具有关联性。 赋值'='排在最后,并且具有从右到左的关联性。
系统使用堆栈来评估表达式。表达式从左到右求值。
现在是原始问题:
this.renderer.setAttribute(this.miniMap.getContainer(), '[@slideInOutLeftTop]', 'sidebarLeftTopState');
第一个x(1)将被压入堆栈。 然后将评估内部(x = y)并将其推入值x(3)的堆栈。 现在将x(1)与x(3)进行比较,因此结果为假。
int x = 1;
int y = 3;
System.out.println(x == (x = y)); // false
在这里, (x = y)将被求值,现在x值变为3,x(3)将被压入堆栈。 现在等式后具有更改后的值的x(3)将被压入堆栈。 现在将对表达式进行求值,并且两者将相同,因此结果为true。
答案 5 :(得分:12)
不一样。始终会先评估左侧,然后评估右侧,方括号中未指定执行顺序,而是指定了一组命令。
使用:
x == (x = y)
基本上,您的操作与:
x == y
并且 x 在比较后的值为 y 。
同时:
(x = y) == x
基本上,您的操作与:
x == x
在 x 取 y 的值之后。并且它将始终返回 true 。
答案 6 :(得分:9)
在第一个测试中,您检查的是1 == 3。
在第二项测试中,您的检查结果为3 == 3。
(x = y)分配值,并对该值进行测试。在前一个示例中,首先x = 1,然后分配x3。1 == 3吗?
在后者中,x被分配为3,并且显然仍为3。3 == 3吗?
答案 7 :(得分:8)
考虑另一个可能更简单的示例:
int x = 1;
System.out.println(x == ++x); // false
x = 1; // reset
System.out.println(++x == x); // true
在这里,必须在进行比较之前在{em>之前应用++x
中的预递增运算符–就像您示例中的(x = y)
必须在之前计算< / em>比较。
但是,表达式评估仍然是从左→到→右进行的,因此第一个比较实际上是1 == 2
,而第二个比较是2 == 2
。
您的示例中发生了同样的事情。
答案 8 :(得分:8)
从左到右评估表达式。在这种情况下:
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
答案 9 :(得分:5)
基本上第一个语句x的值为1 因此,Java将1 ==与新的x变量进行比较
在第二个中,您说x = y意味着x的值已更改,因此当您再次调用它时,它将是相同的值,因此为什么它是true且x == x
答案 10 :(得分:3)
==是一个比较相等运算符,它从左到右起作用。
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
因此,括号仅在算术表达式中起作用,而在比较表达式中不起作用。
答案 11 :(得分:2)
这里的事情是两个算子==
与==
中的算术算子/关系算子优先级顺序,优势算子是=
(关系算子主导),因为它先于{{ 1}}赋值运算符。
尽管具有优先级,但评估顺序为LTR(从左到右),评估顺序后优先。
因此,无论约束评估是什么,LTR。
答案 12 :(得分:-1)
很容易在左边的第二个比较中,在将y赋给x(左边)后比较3 == 3,然后将x = 1与新的赋值x = 3进行比较。似乎总是从x的左到右采用当前状态读取语句。
答案 13 :(得分:-2)
如果您要编写Java编译器或测试程序以验证Java编译器是否正常运行,那么您提出的问题是一个很好的问题。在Java中,这两个表达式必须产生您看到的结果。例如,在C ++中,它们不必这样做-因此,如果有人在其Java编译器中重用了C ++编译器的某些部分,则理论上您可能会发现该编译器的运行不正常。
作为软件开发人员,编写可读性,可理解性和可维护性的代码,这两个版本的代码都将被认为很糟糕。要了解代码的作用,必须完全了解Java语言的定义方式。既写Java代码又写C ++代码的人会不寒而栗。如果您必须问为什么一行代码可以完成它,那么您应该避免使用该代码。 (我想并希望那些正确回答了“为什么”问题的人也会避免使用该代码。)