Why does this go into an infinite loop?

Tom Brito picture Tom Brito · Sep 30, 2010 · Viewed 23.1k times · Source

I have the following code:

public class Tests {
    public static void main(String[] args) throws Exception {
        int x = 0;
        while(x<3) {
            x = x++;
            System.out.println(x);
        }
    }
}

We know he should have writen just x++ or x=x+1, but on x = x++ it should first attribute x to itself, and later increment it. Why does x continue with 0 as value?

--update

Here's the bytecode:

public class Tests extends java.lang.Object{
public Tests();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   iconst_3
   4:   if_icmpge   22
   7:   iload_1
   8:   iinc    1, 1
   11:  istore_1
   12:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:  iload_1
   16:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   19:  goto    2
   22:  return

}

I'll read about the instructions to try to understand...

Answer

Dan Tao picture Dan Tao · Sep 30, 2010

Note: Originally I posted C# code in this answer for purposes of illustration, since C# allows you to pass int parameters by reference with the ref keyword. I've decided to update it with actual legal Java code using the first MutableInt class I found on Google to sort of approximate what ref does in C#. I can't really tell if that helps or hurts the answer. I will say that I personally haven't done all that much Java development; so for all I know there could be much more idiomatic ways to illustrate this point.


Perhaps if we write out a method to do the equivalent of what x++ does it will make this clearer.

public MutableInt postIncrement(MutableInt x) {
    int valueBeforeIncrement = x.intValue();
    x.add(1);
    return new MutableInt(valueBeforeIncrement);
}

Right? Increment the value passed and return the original value: that's the definition of the postincrement operator.

Now, let's see how this behavior plays out in your example code:

MutableInt x = new MutableInt();
x = postIncrement(x);

postIncrement(x) does what? Increments x, yes. And then returns what x was before the increment. This return value then gets assigned to x.

So the order of values assigned to x is 0, then 1, then 0.

This might be clearer still if we re-write the above:

MutableInt x = new MutableInt();    // x is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
x = temp;                           // Now x is 0 again.

Your fixation on the fact that when you replace x on the left side of the above assignment with y, "you can see that it first increments x, and later attributes it to y" strikes me as confused. It is not x that is being assigned to y; it is the value formerly assigned to x. Really, injecting y makes things no different from the scenario above; we've simply got:

MutableInt x = new MutableInt();    // x is 0.
MutableInt y = new MutableInt();    // y is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
y = temp;                           // y is still 0.

So it's clear: x = x++ effectively does not change the value of x. It always causes x to have the values x0, then x0 + 1, and then x0 again.


Update: Incidentally, lest you doubt that x ever gets assigned to 1 "between" the increment operation and the assignment in the example above, I've thrown together a quick demo to illustrate that this intermediate value does indeed "exist," though it will never be "seen" on the executing thread.

The demo calls x = x++; in a loop while a separate thread continuously prints the value of x to the console.

public class Main {
    public static volatile int x = 0;

    public static void main(String[] args) {
        LoopingThread t = new LoopingThread();
        System.out.println("Starting background thread...");
        t.start();

        while (true) {
            x = x++;
        }
    }
}

class LoopingThread extends Thread {
    public @Override void run() {
        while (true) {
            System.out.println(Main.x);
        }
    }
}

Below is an excerpt of the above program's output. Notice the irregular occurrence of both 1s and 0s.

Starting background thread...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1