Why is x == (x = y) not the same as (x = y) == x?

19,409

Solution 1

which, by the order implied by brackets, should be calculated first

No. It is a common misconception that parentheses have any (general) effect on calculation or evaluation order. They only coerce the parts of your expression into a particular tree, binding the right operands to the right operations for the job.

(And, if you don't use them, this information comes from the "precedence" and associativity of the operators, something that's a result of how the language's syntax tree is defined. In fact, this is still exactly how it works when you use parentheses, but we simplify and say that we're not relying on any precedence rules then.)

Once that's done (i.e. once your code has been parsed into a program) those operands still need to be evaluated, and there are separate rules about how that is done: said rules (as Andrew has shown us) state that the LHS of each operation is evaluated first in Java.

Note that this is not the case in all languages; for example, in C++, unless you're using a short-circuiting operator like && or ||, the evaluation order of operands is generally unspecified and you shouldn't rely on it either way.

Teachers need to stop explaining operator precedence using misleading phrases like "this makes the addition happen first". Given an expression x * y + z the proper explanation would be "operator precedence makes the addition happen between x * y and z, rather than between y and z", with no mention of any "order".

Solution 2

== is a binary equality operator.

The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.

Java 11 Specification > Evaluation Order > Evaluate Left-Hand Operand First

Solution 3

As LouisWasserman said, the expression is evaluated left to right. And java doesn't care what "evaluate" actually does, it only cares about generating a (non volatile, final) value to work with.

//the example values
x = 1;
y = 3;

So to calculate the first output of System.out.println(), the following is done:

x == (x = y)
1 == (x = y)
1 == (x = 3) //assign 3 to x, returns 3
1 == 3
false

and to calculate the second:

(x = y) == x
(x = 3) == x //assign 3 to x, returns 3
3 == x
3 == 3
true

Note that the second value will always evaluate to true, regardless of the initial values of x and y, because you are effectively comparing the assignment of a value to the variable it is assigned to, and a = b and b will, evaluated in that order, always be the same by definition.

Solution 4

I'm not sure if there is an item in the Java Language Specification that dictates loading the previous value of a variable...

There is. Next time you are unclear what the specification says, please read the specification and then ask the question if it is unclear.

... the right side (x = y) which, by the order implied by brackets, should be calculated first.

That statement is false. Parentheses do not imply an order of evaluation. In Java, the order of evaluation is left to right, regardless of parentheses. Parentheses determine where the subexpression boundaries are, not the order of evaluation.

Why does the first expression evaluate to false, but the second evaluate to true?

The rule for the == operator is: evaluate the left side to produce a value, evaluate the right side to produce a value, compare the values, the comparison is the value of the expression.

In other words, the meaning of expr1 == expr2 is always the same as though you had written temp1 = expr1; temp2 = expr2; and then evaluated temp1 == temp2.

The rule for the = operator with a local variable on the left side is: evaluate the left side to produce a variable, evaluate the right side to produce a value, perform the assignment, the result is the value that was assigned.

So put it together:

x == (x = y)

We have a comparison operator. Evaluate the left side to produce a value -- we get the current value of x. Evaluate the right side: that's an assignment so we evaluate the left side to produce a variable -- the variable x -- we evaluate the right side -- the current value of y -- assign it to x, and the result is the assigned value. We then compare the original value of x to the value that was assigned.

You can do (x = y) == x as an exercise. Again, remember, all the rules for evaluating the left side happen before all the rules of evaluating the right side.

I would have expected (x = y) to be evaluated first, and then it would compare x with itself (3) and return true.

Your expectation is based on a set of incorrect beliefs about the rules of Java. Hopefully you now have correct beliefs and will in the future expect true things.

This question is different from "order of evaluation of subexpressions in a Java expression"

This statement is false. That question is totally germane.

x is definitely not a 'subexpression' here.

This statement is also false. It is a subexpression twice in each example.

It needs to be loaded for the comparison rather than to be 'evaluated'.

I have no idea what this means.

Apparently you still have many false beliefs. My advice is that you read the specification until your false beliefs are replaced by true beliefs.

The question is Java-specific and the expression x == (x = y), unlike far-fetched impractical constructs commonly crafted for tricky interview questions, came from a real project.

The provenance of the expression is not relevant to the question. The rules for such expressions are clearly described in the specification; read it!

It was supposed to be a one-line replacement for the compare-and-replace idiom

Since that one-line replacement caused a great deal of confusion in you, the reader of the code, I would suggest that it was a poor choice. Making the code more concise but harder to understand is not a win. It is unlikely to make the code faster.

Incidentally, C# has compare and replace as a library method, which can be jitted down to a machine instruction. I believe Java does not have such a method, as it cannot be represented in the Java type system.

Solution 5

It is related to operator precedence and how operators are getting evaluated.

Parentheses '()' has higher precedence and has associativity left to right. Equality '==' come next in this question and has associativity left to right. Assignment '=' come last and has associativity right to left.

System use stack to evaluate expression. Expression gets evaluated left to right.

Now comes to original question:

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

First x(1) will be pushed to stack. then inner (x = y) will be evaluated and pushed to stack with value x(3). Now x(1) will be compared against x(3) so result is false.

x = 1; // reset
System.out.println((x = y) == x); // true

Here, (x = y) will be evaluated, now x value become 3 and x(3) will be pushed to stack. Now x(3) with changed value after equality will be pushed to stack. Now expression will be evaluated and both will be same so result is true.

Share:
19,409

Related videos on Youtube

John McClane
Author by

John McClane

Updated on June 14, 2022

Comments

  • John McClane
    John McClane almost 2 years

    Consider the following example:

    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
         }
    }
    

    I'm not sure if there is an item in the Java Language Specification that dictates loading the previous value of a variable for comparison with the right side (x = y) which, by the order implied by brackets, should be calculated first.

    Why does the first expression evaluate to false, but the second evaluate to true? I would have expected (x = y) to be evaluated first, and then it would compare x with itself (3) and return true.


    This question is different from order of evaluation of subexpressions in a Java expression in that x is definitely not a 'subexpression' here. It needs to be loaded for the comparison rather than to be 'evaluated'. The question is Java-specific and the expression x == (x = y), unlike far-fetched impractical constructs commonly crafted for tricky interview questions, came from a real project. It was supposed to be a one-line replacement for the compare-and-replace idiom

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

    which, being even simpler than x86 CMPXCHG instruction, deserved a shorter expression in Java.

    • Louis Wasserman
      Louis Wasserman over 5 years
      The left hand side is always evaluated before the right hand side. The brackets don't make a difference to that.
    • John McClane
      John McClane over 5 years
      @LouisWasserman The term evaluate is, in my opinion, inapplicable here because x does not need to be evaluated, it just loaded from memory.
    • Louis Wasserman
      Louis Wasserman over 5 years
      Evaluating the expression x = y is certainly relevant, and causes the side effect that x is set to the value of y.
    • John McClane
      John McClane over 5 years
      @LouisWasserman Sure. I knew that, and it was the main idea that lead me to quickReplaceAndCompare().
    • jpmc26
      jpmc26 over 5 years
      Do yourself and your teammates a favor and don't mix state mutation into the same line as state examination. Doing so drastically reduces the readability of your code. (There are some cases where it's absolutely necessary because of atomicity requirements, but functions for those already exist and their purpose would be instantly recognized.)
    • Cort Ammon
      Cort Ammon over 5 years
      For an example of the frustration "shortcuts" like this can cause, it took me the better part of a minute to process this fully enough to see what these are doing. I also know C++, and the rules are different in C++ than they are here in Java. In fact, in C++ this is undefined behavior, so it throws up all sorts of mental red flags for me, slowing me down more than they probably should. I probably read through replaceAndCompare 100 times faster than I did quickReplaceAndCompare.
    • klutt
      klutt over 5 years
      The real question is why you want to write code like this.
    • haccks
      haccks over 5 years
      Never write such code in production.
    • user202729
      user202729 over 5 years
    • Lorinczy Zsigmond
      Lorinczy Zsigmond over 5 years
      Off: if it were C, it would be a clear case of UB, meaning the compiler may generate anything from it.
    • nigel222
      nigel222 over 5 years
      Somebody once told me that all boolean expressions have three possible values: True, False and "Bloody Stupid".
    • Taemyr
      Taemyr over 5 years
      @jpmc26 Does constructs such as the ones in OP actually give any guarantee of atomicity?
    • Eric Lippert
      Eric Lippert over 5 years
      The key to your question is your false belief that parentheses imply evaluation order. That is a common belief because of how we're taught math in elementary school and because some beginner programming books still get it wrong, but it is a false belief. This is a pretty frequent question. You might benefit from reading my articles on the subject; they are about C# but they apply to Java: ericlippert.com/2008/05/23/precedence-vs-associativity-vs-or‌​der ericlippert.com/2009/08/10/precedence-vs-order-redux
    • jpmc26
      jpmc26 over 5 years
      @Taemyr No, I'm referring to things like this. Atomic operations are explicit, hence why they're instantly recognized. Sorry for not being more clear.
    • Eric Lippert
      Eric Lippert over 5 years
      In particular, consider something like x() * (y() + z()) The order of function calls is not y then z then x because "the parentheses come first". The parentheses determine the subexpression boundaries, not the order of evaluation. The * has two subexpressions: x() and (y() + z()). The left one, x(), happens first. Then the right one happens: y() is called, z() is called, and they are summed. Then the multiplication happens. So the + happens before the *, as it must, but the operands to the plus do not happen first.
    • dan04
      dan04 over 5 years
      @EricLippert: I think that part of the problem is that math teachers refer to operator precedence rules as the “order of operations”, and the word “order” trips people up.
    • Weeble
      Weeble over 5 years
      "x is definitely not a 'subexpression' here" - It really is. Why do you think that "loading" the value out of a variable is not at instance of "evaluation"? Evaluation yields a value, not a variable or a memory location.
    • Eric Lippert
      Eric Lippert over 5 years
      @Weeble: The original poster is, as you note, somewhat confused. However your statement that evaluation yields a value, not a variable, is not supported by evidence. Consider for example array()[index()] = value(); The semantics of Java are that we call array(), then index(), then value(); all of these are values. But we must then check whether the array reference is valid and throw if it is not, and then check whether the index is valid, and throw if it is not. But absolutely the subexpression to the left of the assignment is not a value. It's a variable.
    • Eric Lippert
      Eric Lippert over 5 years
      @Weeble: Your statement is also directly contradicted by the Java specification. Languages like C distinguish between "lvalues" and "rvalues"; an "lvalue" is the "value" produced by evaluating the left side of an assignment. This is rightly criticized as being confusing; Java (and C#) avoid this problem by eschewing this notion that assignable things are "values", and instead call them what they are: variables. The Java specification for assignment makes it very clear: "First, the left-hand operand is evaluated to produce a variable..."
    • LarsH
      LarsH over 5 years
      @OP "x is definitely not a 'subexpression' here" - on what basis do you think this?
    • John McClane
      John McClane over 5 years
      @LarsH Because expression involves operator(s).
    • Eric Lippert
      Eric Lippert over 5 years
      @JohnMcClane: That statement is false; an expression need not involve any operator. Again, you would do very well to read the specification, because you have a great many false beliefs. Disabuse yourself of them!
    • LarsH
      LarsH over 5 years
      @JohnMcClane In order to see from the spec (docs.oracle.com/javase/specs/jls/se11/html/jls-15.html), that there are expressions without operators, you can follow the syntax of the Expression terminal through several levels, through PostFixExpression and ExpressionName. Or look at MethodInvocation, whose ArgumentList consists of comma-separated expressions. If every expression were required to involve operators, you couldn't pass x as an argument to a method.
    • Andrew Tobilko
      Andrew Tobilko over 5 years
      @JohnMcClane an expression doesn't necessarily involve operator(s). Here, you'll see that Expression can be a NameExpression (e.g. denoting a local variable), a PrimaryExpression (e.g. referring to a primitive literal), etc
    • John McClane
      John McClane over 5 years
      @jpmc26 Look at the implementation of Map.computeIfAbsent() and blame its author too. Yeah I know that they are allowed to do this and I'm not :)
    • Weeble
      Weeble over 5 years
      @EricLippert I think we are talking at cross-purposes here, since the identifier "x" appears twice in the expression under discussion, once on the left of the equality operator, and one on the left of an assignment operator. I assumed that the discussion was about the former, since the OP talks about "loading" the value. The part of the spec you quote is about the left operand of an assignment operator. I concede I spoke imprecisely. My point is that "evaluating" the left side expression doesn't yield some kind of lazy value that can shift or change before the comparison operation evaluates.
    • Weeble
      Weeble over 5 years
      @EricLippert - My interpretation of the OP's mental model was: 1. evaluate == operands left to right. 2. x doesn't need evaluation, it's just "x" (a mistake). 3. evaluate (x = y) 4. Variable x is updated to the value of y, the expression has the value of y, which is 3. 5. evaluate == operator. 6. Retrieve the current value of x and compare it to 5. If that's the case, it's not the order of evaluation they've gotten confused about. It's the meaning of "evaluating x".
  • Mr Lister
    Mr Lister over 5 years
    The wording "appears to be" doesn't sound like they're sure, tbh.
  • Robyn
    Robyn over 5 years
    "appears to be" means the specification does not require that the operations are actually carried out in that order chronologically, but it requires that you get the same result that you would get if they were.
  • Kelvin
    Kelvin over 5 years
    @MrLister "appears to be" appears to be a poor word choice on their part. By "appear" they mean "manifest as a phenomenon to the developer". "is effectively" may be a better phrase.
  • Michael Edenfield
    Michael Edenfield over 5 years
    in the C++ community this is the equivalent to the "as-if" rule... the operand is required to behave "as if" it was implemented per the following rules, even if technically it is not.
  • John P
    John P over 5 years
    I wish my teachers had made some separation between the underlying math and the syntax they used to represent it, like if we spent a day with Roman numerals or Polish notation or whatever and saw that addition has the same properties. We learned associativity and all those properties in middle school, so there was plenty of time.
  • MC Emperor
    MC Emperor over 5 years
    @Kelvin I agree, I would have chosen that word either, rather than "appears to be".
  • John McClane
    John McClane over 5 years
    If anyone could go through the whole JLS, then there would be no reason to publish Java books and at least half of this site would be useless too.
  • Eric Lippert
    Eric Lippert over 5 years
    @JohnMcClane: I assure you, there is no difficulty whatsoever in going through the entire specification, but also, you don't have to. The Java specification begins with a helpful "table of contents" which will help you quickly get to the parts you are most interested in. It's also online and keyword searchable. That said, you are correct: there are lots of good resources that will help you learn how Java works; my advice to you is that you make use of them!
  • Aisah Hamzah
    Aisah Hamzah over 5 years
    Glad you mentioned that this rule doesn't hold true for all languages. Also, if either side has another side-effect, like writing to a file, or reading the current time, it is (even in Java) undefined in what order that happens. However, the result of the comparison will be as if it was evaluated left-to-right (in Java). Another aside: quite a few languages simply disallow mixing assignment and comparison this way by syntax rules, and the issue wouldn't arise.
  • jpmc26
    jpmc26 over 5 years
    @Kelvin The more precise phrasing would be some form of "equivalent to."
  • Harper - Reinstate Ukraine
    Harper - Reinstate Ukraine over 5 years
    "Left to right" is also true in math, by the way, just when you get to a parentheses or precedence, you iterate inside them and eval everything inside left to right before going any further on the main tier. But math would never do this; the distinction only matters because this is not an equation but a combo operation, doing both an assignment and an equation in one swoop. I would never do this because readability is poor, unless I was doing code golf or was looking for a way to optimize performance, and then, there would be comments.
  • John McClane
    John McClane over 5 years
    There is a sentence in 15.21 (I wonder why you haven't cited it) that more precisely describes the issue we deal with here: "The equality operators are commutative if the operand expressions have no side effects." Here, the assignment is that side effect and, consequently, we have encountered the situation when the equality operator turned out to be non-commutative.
  • John McClane
    John McClane over 5 years
    Anyway, thank you for the contribution you have made into this question and, particularly, for being the first to provide the links to exact places in JLS. I hope that users (which were mostly able to distinguish between living and dead questions) appreciated your answer too.
  • Brian
    Brian over 5 years
    @JohnP: It gets worse. Does 5*4 mean 5+5+5+5 or 4+4+4+4+4 ? Some teachers insist that only one of those choices is right.
  • Lightness Races in Orbit
    Lightness Races in Orbit over 5 years
    @Brian But... but... multiplication of real numbers is commutative!
  • walen
    walen over 5 years
    This answer is unnecessarily condescending and rude. Remember: be nice.
  • Eric Lippert
    Eric Lippert over 5 years
    @LuisG.: No condescension is intended or implied; we are all here to learn from each other, and I am not recommending anything I have not done myself when I was a beginner. Nor is it rude. Clearly and unambiguously identifying their false beliefs is a kindness to the original poster. Hiding behind "politeness" and allowing people to continue to have false beliefs is unhelpful, and reinforces bad habits of thought.
  • Eric Lippert
    Eric Lippert over 5 years
    @LuisG.: I used to write a blog about the design of JavaScript, and the most helpful comments I ever got were from Brendan pointing out clearly and unambiguously where I got it wrong. That was great and I appreciated him taking the time, because I then lived the next 20 years of my life not repeating that mistake in my own work, or worse, teaching it to others. It also gave me the opportunity to correct those same false beliefs in others by using myself as an example of how people come to believe false things.
  • syck
    syck over 5 years
    In my world of thinking, a pair of parentheses represents "is needed for". Calculating ´a*(b+c)´, the parentheses would express that the result of the addition is needed for the multiplication. Any implicit operator preferences can be expressed by parens, except LHS-first or RHS-first rules. (Is that true?) @Brian In math there are a few rare cases where multiplication can be substituted by repeated addition but that is by far not always true (starting with complex numbers but not limited to). So your educators should really have an eye on what the are telling people....
  • syck
    syck over 5 years
    I could not figure out which part of my expression evaluates to true because you did not give any association rules. TL;DR: which part? :)
  • JJJ
    JJJ over 5 years
    The conclusion is wrong. The behavior is not different between arithmetic and comparison operators. x + (x = y) and (x = y) + x would show similar behavior as the original with comparison operators.
  • Nisrin Dhoondia
    Nisrin Dhoondia over 5 years
    @JJJ In x+(x=y) and (x=y)+x there is no comparison involved, it is just assigning y value to x and adding it to x.
  • JJJ
    JJJ over 5 years
    ...yes, that is the point. "Parentheses plays its major role in arithmetic expressions only not in comparison expressions" is wrong because there is no difference between arithmetic and comparison expressions.
  • John P
    John P over 5 years
    @LightnessRacesinOrbit I assumed that was in the abstract, as in X*Y is either X Y's or Y X's. But X Y's / Y X's / etc. is semantics, not syntax, AFAIK.
  • Lightness Races in Orbit
    Lightness Races in Orbit over 5 years
    @JohnP Sorry, not sure what you're replying to?
  • John P
    John P over 5 years
    @LightnessRacesinOrbit "But... but... multiplication of real numbers is commutative!" 4 and 5 are reals, but I think they were meant to be placeholders. Whether or not multiplication is commutative for given types is semantics though, which would make it moot.
  • Kindred
    Kindred over 5 years
    "To understand what the code does, one has to know exactly how the Java language is defined." But what if every coworker consider it a common sense?
  • trincot
    trincot over 5 years
    I think there is a difference to make between whether the used language is intended as a favour, and is effective on the one hand and whether it fits with the code of conduct on the other. It is of course not the aim to have people continue some bad habits, but people can be made aware of them in more than one way.
  • JJJ
    JJJ over 5 years
    The answer is wrong. Operator precedence doesn't affect evaluation order. Read some of the top-voted answers for explanation, especially this one.
  • Himanshu
    Himanshu over 5 years
    Correct, its actually the way we are taught the illusion of restrictions on precedence comes n all those things but correctly pointed it has no impact coz the order of evaluation remains left to right
  • Kirk Woll
    Kirk Woll over 5 years
    To @LuisG.'s point, what about replacing: "There is. Next time you are unclear what the specification says, please read the specification and then ask the question if it is unclear." With something like: "I think you're expecting your results to be due to thinking that X is true of Java. I believe that in fact Y is true. Let me explain..." Just seems all around more congenial to me?
  • Eric Lippert
    Eric Lippert over 5 years
    @KirkWoll: Because that completely misses the point of the advice, which is the specification is there for people to read it to get their questions answered. Your proposed replacement does not provide a tool that helps the original poster. It instead replaces it with a series of musing about various people's mental states. That does not get the original poster to do what is correct, which is to read the specification when they have a question about the specification of the language. This should not be controversial.
  • Eric Lippert
    Eric Lippert over 5 years
    @KirkWoll: Now, I strongly agree that being congenial is great; that's what the "please" is for in "please read the specification".
  • Kirk Woll
    Kirk Woll over 5 years
    EricLippert I respect you greatly, but I do believe there is room for some middle ground between being forceful and abrupt with one's recommendations (and sorry to say, I do think that is the case here) and being empathic. IMO, your recommendations have a feel of coarse thread when fine thread would work as well. ¯\_(ツ)_/¯ And it's true, my original suggestion would not provide the vehicle towards enlightenment you seek immediately, but I believe it would lead there with less friction and greater cooperation eventually.
  • Eric Lippert
    Eric Lippert over 5 years
    @KirkWoll: Your beliefs are noted. While we're noting beliefs, my belief is that if people spent more of their valuable time ensuring correctness and consulting references, and less time complaining about tone rather than substantive critiques, more value would be added to the world, and less of my time would be wasted, but that's just my belief.
  • Kirk Woll
    Kirk Woll over 5 years
    That's fair. I just don't think it's really zero sum. Sometimes you can accomplish your objectives on both axis -- you can correct misinformation and also do so while being diplomatic without sacrificing anything. IMO, I'm not convinced this thread is a good example of that so far. You suggest that "less time complaining about tone rather than substantive critiques" is better -- but why does it have to be an either/or question? IMO, both are achievable, and moreover, both are necessary.