考虑下面的例子:

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)进行比较,根据括号所暗示的顺序,应该首先计算右边的值。

为什么第一个表达式的值为假,而第二个表达式的值为真?我希望(x = y)首先被求值,然后它将x与自身(3)进行比较并返回true。


这个问题与Java表达式中子表达式的求值顺序不同,因为x在这里肯定不是“子表达式”。它需要被加载来进行比较,而不是被“评估”。这个问题是java特有的,表达式x == (x = y)与通常为棘手的面试问题而设计的牵强的不切实际的构造不同,它来自一个真实的项目。它应该是比较-替换习惯用法的一行替换

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

它比x86的CMPXCHG指令更简单,在Java中应该有更短的表达式。


当前回答

==是一个二进制相等运算符。

二进制操作符的左操作数似乎在右操作数的任何部分被求值之前就已经完全求值了。 Java 11规范>求值顺序>首先求左操作数

其他回答

我不确定Java语言规范中是否有规定加载变量的前一个值…

有。下次当你不清楚说明书上写了什么时,请先阅读说明书,如果不清楚就提出问题。

... 右边(x = y),根据括号所示的顺序,应该先计算。

这种说法是错误的。括号并不表示求值的顺序。在Java中,求值的顺序是从左到右,不管括号是什么。括号决定子表达式的边界,而不是求值的顺序。

为什么第一个表达式的值为假,而第二个表达式的值为真?

==运算符的规则是:左边求值产生一个值,右边求值产生一个值,比较两个值,比较的就是表达式的值。

换句话说,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和replace作为一个库方法,它可以被简化为一条机器指令。我相信Java没有这样的方法,因为它不能在Java类型系统中表示。

表达式从左到右求值。在这种情况下:

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

正如LouisWasserman所说,表达式从左到右求值。java并不关心“evaluate”实际上做了什么,它只关心生成一个(非易失性的,最终的)值。

//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的赋值顺序总是相同的。

如果您想编写Java编译器,或者测试程序以验证Java编译器是否正常工作,那么您提出的这类问题是一个非常好的问题。在Java中,这两个表达式必须产生您所看到的结果。例如,在c++中,它们就不需要这样做——所以如果有人在他们的Java编译器中重用了c++编译器的某些部分,理论上你可能会发现编译器没有像它应该的那样工作。

As a software developer, writing code that is readable, understandable and maintainable, both versions of your code would be considered awful. To understand what the code does, one has to know exactly how the Java language is defined. Someone who writes both Java and C++ code would shudder looking at the code. If you have to ask why a single line of code does what it does, then you should avoid that code. (I suppose and hope that the guys who answered your "why" question correctly will themselves avoid that ind of code as well).

==是一个比较相等运算符,它从左到右工作。

x == (x = y);

这里x的旧赋值与x的新赋值进行比较,(1==3)//false

(x = y) == 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

因此,括号在算术表达式中起主要作用,而不是在比较表达式中起主要作用。