请考虑以下示例:
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> Andrew Tobil..:
==
是二元相等运算符.
在评估右侧操作数的任何部分之前,似乎完全评估了二元运算符的左侧操作数.
Java 11规范>评估顺序>首先评估左侧操作数
"似乎是"意味着规范并不要求按时间顺序按顺序执行操作,但它要求您获得与之相同的结果.
措辞"似乎是"听起来并不像他们确定,tbh.
@MrLister"似乎"似乎是他们选择的一个糟糕的词."出现"意味着"向开发者表现为现象"."有效"可能是一个更好的短语.
在C++社区中,这相当于"as-if"规则......操作数需要表现为"好像"它是按照以下规则实现的,即使技术上并非如此.
@Kelvin我同意,我也会选择这个词,而不是"看起来像是".
2> 小智..:
正如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
需要注意的是第二个值将始终为true,无论初始值的x
和y
,因为你有效地比较值将其分配给变量的赋值,a = b
并b
会以该顺序进行评估,始终是相同的根据定义.
3> Lightness Ra..:
按括号隐含的顺序,应首先计算
不是.通常的误解是括号对计算或评估顺序有任何(一般)影响.它们只将表达式的部分强制转换为特定的树,将正确的操作数绑定到作业的正确操作.
(并且,如果你不使用它们,这些信息来自操作符的"优先级"和关联性,这是语言的语法树定义的结果.实际上,这仍然是它的工作原理使用括号,但我们简化并说我们当时不依赖于任何优先规则.)
一旦完成(即一旦你的代码被解析成一个程序),那些操作数仍然需要被评估,并且有关于如何完成的单独规则:所述规则(如安德鲁告诉我们的那样)说明每个操作的LHS首先在Java中进行评估.
请注意,并非所有语言都是如此; 例如,在C++中,除非您使用的是短路运营商像&&
或者||
,操作数的计算顺序一般是不明确的,你不应该依赖于它无论哪种方式.
教师需要使用诸如"这使得添加首先发生"之类的误导性短语来停止解释运算符优先级.给出一个表达式x * y + z
,正确的解释是"运算符优先级使得加法发生在x * y
和之间z
,而不是在y
和之间z
",没有提到任何"顺序".
我希望我的老师在基础数学和他们用来表示它的语法之间做了一些分离,比如我们花了一天时间用罗马数字或波兰符号或其他什么,并看到添加具有相同的属性.我们在中学学习了相关性和所有这些属性,所以有充足的时间.
@JohnP:情况变得更糟.5*4是5 + 5 + 5 + 5还是4 + 4 + 4 + 4 + 4?有些老师坚持认为只有其中一种选择是正确的.
@Brian但是......但......实数的乘法是可交换的!
在我的思考世界中,一对括号表示"需要".计算'a*(b + c)',括号表示加法的结果_需要乘法.任何隐式操作员首选项都可以由parens,_except_ LHS-first或RHS-first规则表示.(这是真的吗?)@ Brian在数学中有一些罕见的情况,乘法可以通过重复加法来代替,但到目前为止并不总是正确的(从复数开始但不限于此).所以你的教育工作者应该真正关注那些告诉人们的事情......
4> Eric Lippert..:
我不确定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#已经比较和替换为库方法,可以将其下载到机器指令.我相信Java没有这样的方法,因为它无法在Java类型系统中表示.
如果有人可以通过整个JLS,那么没有理由发布Java书籍,至少有一半的这个网站也没用.
@JohnMcClane:我向你保证,在完成整个规范时没有任何困难,但你也没有.Java规范从一个有用的"目录"开始,它将帮助您快速访问您最感兴趣的部分.它还可以在线和关键字搜索.那就是说,你是对的:有很多好的资源可以帮助你了解Java的工作原理; 我给你的建议是你利用它们!
这个答案是不必要的居高临下和粗鲁.记住:[很高兴](/sf/ask/17360801/).
@LuisG.:无意或暗示的屈尊; 我们都在这里互相学习,我不推荐任何我初学时没有做过的事情.也不粗鲁.**明确无误地确定他们的错误信念是对原始海报**的善意.躲在"礼貌"背后,让人们继续拥有错误的信念是*无益的*,*强化了不良的思想习惯*.
@LuisG.:我曾经写过一篇关于Javascript设计的博客,我收到的最有帮助的评论来自Brendan,清楚而明确地指出我弄错了.这很棒,我很感激他花时间,因为我生活在接下来的20年里,不是在我自己的工作中重复这个错误,或者更糟糕的是,教给别人.它还让我有机会通过将自己作为人们如何相信虚假事物的一个例子来纠正其他人的错误信念.
EricLippert我非常尊重你,但我确实认为在强烈和突然之间有一些中间立场与一个人的建议(并且很遗憾地说,我认为这就是这种情况)和同情.IMO,你的建议有一个粗线程的感觉,当细线也可以工作.¯\\ _(ツ)_ /¯而且这是真的,我的原始建议不会为你立即寻求的启蒙提供工具,但我相信它会带来更少的摩擦和更大的合作.
5> Amit..:
它与运算符优先级以及如何评估运算符有关.
括号'()'具有更高的优先级,并且从左到右具有关联性.平等'=='在这个问题中接下来,并且从左到右具有相关性.作业'='是最后一个,从右到左有结合性.
系统使用堆栈来评估表达式.表达式从左到右进行评估.
现在来到原始问题:
int x = 1;
int y = 3;
System.out.println(x == (x = y)); // false
首先将x(1)推到堆叠状态.然后将评估内部(x = y)并将其推送到值为x(3)的堆栈.现在x(1)将与x(3)进行比较,因此结果为假.
x = 1; // reset
System.out.println((x = y) == x); // true
这里,将评估(x = y),现在x值变为3,x(3)将被推送到堆栈.现在x(3)在相等之后具有更改的值将被推送到堆栈.现在将对表达式进行求值,两者都是相同的,因此结果为真.
6> Or10n..:
它不一样.左侧将始终在右侧之前进行评估,并且括号不指定执行顺序,而是指定一组命令.
附:
x == (x = y)
你基本上是这样做的:
x == y
并且在比较之后x将具有y的值.
同时:
(x = y) == x
你基本上是这样做的:
x == x
在x取y的价值之后.而且它将永远回归真实.
7> Michael Puck..:
在第一次测试中,你检查1 == 3.
在第二次测试中,您的检查确实为3 == 3.
(x = y)分配值并测试该值.在前一个例子中,x = 1,然后x被赋值为3. 1 == 3?
在后者中,x被赋值为3,显然它仍然是3. 3 == 3?
8> walen..:
考虑另一个,也许更简单的例子:
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> Derviş Kayım..:
表达式从左到右进行评估.在这种情况下:
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