为什么x ==(x = y)与(x = y)== x不同?

时间:2018-12-12 19:11:49

标签: java variable-assignment equality operator-precedence jls

考虑以下示例:

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中使用较短的表达式。

14 个答案:

答案 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

请注意,无论xy的初始值如何,第二个值都将始终为true,因为您正在有效地将值的分配与分配给该变量的变量进行比较,并且a = bb将按照该顺序求值,根据定义,它们始终是相同的。

答案 2 :(得分:96)

  

应按照括号中的顺序先计算

不。一个普遍的误解是括号对计算或评估顺序有任何(一般)影响。它们仅将表达式的各部分强制转换为特定的树,将正确的操作数绑定到作业的正确操作。

(而且,如果不使用它们,则此信息来自运算符的“优先级”和关联性,这是语言的语法树如何定义的结果。实际上,这仍然是它的确切用法。当您使用括号时可以使用,但是我们简化了一点,说我们当时不依赖任何优先级规则。)

一旦完成(即,一旦您的代码被解析成程序),那些操作数仍需要进行评估,并且关于如何完成操作有单独的规则:所述规则(如安德鲁向我们展示的那样)表明LHS首先用Java评估每个操作。

请注意,并非所有语言都如此。例如,在C ++中,除非您使用&&||之类的短路运算符,否则通常不指定操作数的求值顺序,并且您不应以任何一种方式依赖它。

教师需要停止使用误导性短语(例如“使加法首先发生”)来解释运算符的优先级。给定表达式x * y + z,正确的解释是“运算符优先级使加法发生在x * yz之间,而不是yz之间”,其中没有提及任何“订单”。

答案 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 ++代码的人会不寒而栗。如果您必须问为什么一行代码可以完成它,那么您应该避免使用该代码。 (我想并希望那些正确回答了“为什么”问题的人也会避免使用该代码。)