考虑下面的例子:

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语言规范中是否有规定加载变量的前一个值…

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

... 右边(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类型系统中表示。

其他回答

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

根据括号所示的顺序,哪个应该先计算

不。一个常见的误解是,括号对计算或求值顺序有任何(一般的)影响。它们只是将表达式的部分强制到特定的树中,将正确的操作数绑定到作业的正确操作。

(而且,如果不使用它们,这些信息来自操作符的“优先级”和结合性,这是语言语法树定义的结果。事实上,当你使用圆括号时,它仍然是这样工作的,但我们简化了,说我们不依赖于任何优先规则。)

一旦完成了这一点(即一旦你的代码被解析成一个程序),这些操作数仍然需要被求值,并且有关于如何做的单独的规则:所说的规则(正如Andrew向我们展示的那样)声明每个操作的LHS在Java中首先被求值。

注意,并非所有语言都是如此;例如,在c++中,除非使用&&或||这样的短路操作符,否则操作数的求值顺序通常是未指定的,无论如何都不应该依赖于它。

老师们需要停止使用像“这使得加法先发生”这样的误导性短语来解释运算符优先级。给定一个表达式x * y + z,正确的解释应该是“运算符优先级使加法发生在x * y和z之间,而不是在y和z之间”,没有提到任何“顺序”。

如果您想编写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).

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

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

这是不一样的。左边总是在右边之前求值,括号没有指定执行顺序,而是指定命令的分组。

:

      x == (x = y)

你所做的基本上是一样的:

      x == y

x在比较后会得到y的值。

而与:

      (x = y) == x

你所做的基本上是一样的:

      x == x

在x取y的值之后。它总是返回true。