考虑下面的例子:

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中应该有更短的表达式。


正如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 11规范>求值顺序>首先求左操作数


在第一个测试中,您检查的是1 == 3。

在第二个测试中,你的检查是3 == 3。

(x = y)赋值并测试该值。在前面的例子中,x先= 1,然后x被赋值为3。1 = 3吗?

在后者中,x被赋值为3,显然它仍然是3。3 == 3吗?


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

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

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

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

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

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


它与操作符优先级以及如何计算操作符有关。

括号'()'优先级更高,具有从左到右的结合性。 等式'=='在这个问题中是下一个,从左到右具有结合律。 赋值'='排在最后,具有从右到左的结合性。

系统使用堆栈计算表达式。表达式从左向右求值。

现在回到最初的问题:

int x = 1;
int y = 3;
System.out.println(x == (x = y)); // false

首先x(1)将被压入堆栈。 然后inner (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)的值改变后的等式将被推入堆栈。 现在表达式将被求值,两者将相同,因此结果为真。


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

:

      x == (x = y)

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

      x == y

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

而与:

      (x = y) == x

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

      x == x

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


基本上第一个表述x的值是1 Java将1 ==和新的x变量进行比较,这两个变量不一样

在第二个例子中,你说x=y,这意味着x的值改变了,所以当你再次调用它时,它将是相同的值,因此它是正确的,x= =x


考虑另一个更简单的例子:

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。 同样的事情也发生在你的例子中。


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

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

我不确定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类型系统中表示。


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


左边的第二个比较很简单,在把y赋值给x之后(在左边),你就比较3 == 3。在第一个例子中,你比较x = 1和新赋值x = 3。似乎总有一个当前状态从左到右读取x的语句。


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

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

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


这里的问题是算术运算符/关系运算符在两个运算符中的优先顺序= vs =占主导地位的是==(关系运算符占主导地位),因为它在=赋值运算符之前。 不管优先级,求值的顺序是LTR(从左到右)优先级在求值顺序之后。 因此,不考虑任何约束条件,求值就是LTR。