Why does this go into an infinite loop?

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


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

x = x++ works in the following way:

  • First it evaluates expression x++ . Evaluation of this expression produces an expression value (which is the value of x before increment) and increments x .
  • Later it assigns the expression value to x , overwriting incremented value.
  • So, the sequence of events looks like follows (it's an actual decompiled bytecode, as produced by javap -c , with my comments):

       8:   iload_1         // Remember current value of x in the stack
       9:   iinc    1, 1    // Increment x (doesn't change the stack)
       12:  istore_1        // Write remebered value from the stack to x
    

    For comparison, x = ++x :

       8:   iinc    1, 1    // Increment x
       11:  iload_1         // Push value of x onto stack
       12:  istore_1        // Pop value from the stack to x

    This happens because the value of x doesn't get incremented at all.

    x = x++;
    

    is equivalent to

    int temp = x;
    x++;
    x = temp;
    

    Explanation:

    Let's look at the byte code for this operation. Consider a sample class:

    class test {
        public static void main(String[] args) {
            int i=0;
            i=i++;
        }
    }
    

    Now running the class disassembler on this we get:

    $ javap -c test
    Compiled from "test.java"
    class test extends java.lang.Object{
    test();
      Code:
       0:    aload_0
       1:    invokespecial    #1; //Method java/lang/Object."<init>":()V
       4:    return
    
    public static void main(java.lang.String[]);
      Code:
       0:    iconst_0
       1:    istore_1
       2:    iload_1
       3:    iinc    1, 1
       6:    istore_1
       7:    return
    }
    

    Now the Java VM is stack based which means for each operation, the data will be pushed onto the stack and from stack the data will popped out to perform the operation. There is also another data structure, typically an array to store the local variables. The local variables are given ids which are just the indexes to the array.

    Let us look at the mnemonics in main() method:

  • iconst_0 : The constant value 0 is pushed on to the stack.
  • istore_1 : The top element of the stack is popped out and stored in the local variable with index 1
    which is x .
  • iload_1 : The value at the location 1 that is value of x which is 0 , is pushed into the stack.
  • iinc 1, 1 : The value at the memory location 1 is incremented by 1 . So x now becomes 1 .
  • istore_1 : The value at the top of the stack is stored to the memory location 1 . That is 0 is assigned to x overwriting its incremented value.
  • Hence the value of x does not change resulting in the infinite loop.

    链接地址: http://www.djcxy.com/p/97058.html

    上一篇: 我如何在正则表达式的末尾转义大括号

    下一篇: 为什么这会陷入无限循环?