Java assignment operator execution
In Java, I understand that assignment evaluates to the value of the right operand, so statements like x == (y = x)
evaluate to true
.
This code, however, outputs false
.
public static void main(String[]args){
String x = "hello";
String y = "goodbye";
System.out.println(x.equals(x = y));
}
Why is this? In my understanding, it first evaluates (x = y)
, which assigns x
the value of y
, and then returns the value of y
. Then x.equals(y)
is evaluated, which should be true
since x
and y
should share the same references now, but instead, I get false
.
What is happening here?
Solution 1:
First of all: that's an interesting question, but should never come up in "real code", as assigning to the variable you call in the very same line is confusing even if you know how it works.
What happens here is these 3 steps:
- figure out which object to call the method on (i.e. evaluate the first
x
, this will result in a reference to the String "hello") - figure out the parameters (i.e. evaluate
x = y
, which will changex
to point to the String "goodbye" and also return a reference to that String) - call the method
equals
on the result of #1 using the result of #2 as the parameter (which will be references to the Strings "hello" and "goodbye" respectively).
Looking at the byte code produced for that method makes it clear (assuming you're fluent in Java bytecode):
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String goodbye
5: astore_2
6: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_1
10: aload_2
11: dup
12: astore_1
13: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
16: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
19: return
Line #9 is step 1 above (i.e. evaluates x
and remembers the value).
Line #10-12 is step 2. It loads y
, duplicates it (once for assigning, once for the return value of the assignment expression) and assigns it to x
.
Line #13 invokes equals
on the result computed in Line #9 and the result of Lines #10-12.
Solution 2:
Good question! And the JLS has the answer...
§15.12.4.1 (Example 15.12.4.1-2). Evaluation Order During Method Invocation:
As part of an instance method invocation, there is an expression that denotes the object to be invoked. This expression appears to be fully evaluated before any part of any argument expression to the method invocation is evaluated.
So, in:
String x = "hello";
String y = "goodbye";
System.out.println(x.equals(x = y));
the occurrence of x
before .equals
is evaluated first, before the argument expression x = y
.
Therefore, a reference to the string hello
is remembered as the target reference before the local variable x
is changed to refer to the string goodbye
. As a result, the equals
method is invoked for target object hello
with argument goodbye
, so the result of the invocation is false
.
Solution 3:
It's important to remember that a String
in java is an object, and therefore a reference. When you call
x.equals(...)
It is checking if the value at the location currently referenced by x
is equal to what you are passing in. Inside, you are changing the value that x
is referencing, but you are still calling equals
with the original reference (the reference to "hello"). So, right now your code is comparing to see if "hello" is equal to "goodbye", which it clearly is not. After this point, if you use x
again, it will result in a reference to the same value as y.
Solution 4:
x=y
in the parenthesis means that expression (x=y)
is now goodbye
, while the outer x in x.equals
holds the value hello
Solution 5:
Reimus gave the correct answer, but I'd like to elaborate.
In Java (and most languages) the convention is variable goes on the left, assignment on the right.
Let's Break it down:
String x = "hello";
//x <- "hello"
String y = "goodbye";
//y <- "goodbye";
For debugging purposes as well as code readability, it's always a good practice to split up your lines so that they only do one thing.
System.out.println(x.equals(x = y)); //Compound statement
Here, x.equals(...)
is called on the original reference to x, or "hello", it is updated for the second reference.
I would write this as (and this will give you your expected answer):
x = y;
// x <- y = "goodbye"
boolean xEqualsX = x.equals(x);
// xEqualsX <- true
System.out.println(xEqualsX);
// "true"
Now this seems obvious that it should behave this way, but it's also really easy to see exactly what is going on in each line, which is something you should strive for.