为什么这会陷入无限循环?

我有以下代码:

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

我们知道他应该只写x++或者x=x+1 ,但是在x = x++它应该首先将x赋给它自己,然后增加它。 为什么x继续0作为值?

--update

这是字节码:

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

}

我会阅读有关说明以了解...


:最初我在此答案中发布了C#代码以用于说明,因为C#允许您通过引用通过ref关键字传递int参数。 我决定使用我在Google上发现的第一个MutableInt类来对实际的合法Java代码进行更新,以便对C#中ref的大致排序进行排序。 我无法确定这是否有助于或伤害了答案。 我会说我个人并没有完成那么多的Java开发; 所以我知道可以有更多的惯用方法来说明这一点。


也许如果我们写出一个方法来做相当于x++操作,它会使这个更清晰。

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

对? 增加传递的值并返回原始值:这是postincrement运算符的定义。

现在,让我们来看看这个行为在你的示例代码中的表现如何:

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

postIncrement(x)做什么? 增加x ,是的。 然后返回增量之前的x 。 这个返回值然后被分配给x

所以赋值给x的值的顺序是0,然后是1,然后是0。

如果我们重写上面的话,这可能会更清楚:

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.

你注意到当你用x代替上面赋值左边的y ,“你可以看到它先增加x,然后将它归类为y”,这令我感到困惑。 它不是x正被分配给y ; 它是以前分配给x的值 。 真的,注入y与上面的场景没有什么不同, 我们只是得到了:

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.

所以很明显: x = x++实际上不会更改x的值。 它总是使x的值为x0,然后是x0 + 1,然后是x0。


更新 :顺便说一句,为了避免你怀疑x在前面例子中的增量操作和赋值之间被赋值为1,我把一个快速演示放在一起来说明这个中间值确实存在,尽管它将永远不会在执行线程上“看到”。

该演示调用x = x++; 在一个循环中,一个单独的线程连续地将x的值打印到控制台。

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

以下是上述程序输出的摘录。 注意1和0的不规则出现。

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

x = x++工作方式如下:

  • 首先它评估表达式x++ 。 对此表达式的评估会生成一个表达式值(这是增量前x的值)和增量x
  • 稍后,它将表达式值赋给x ,覆盖递增的值。
  • 所以,事件序列如下所示(它是一个实际的反编译的字节码,由javap -c ,并带有我的注释):

       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
    

    为了比较, 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

    发生这种情况是因为x的值根本没有增加。

    x = x++;
    

    相当于

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

    说明:

    我们来看看这个操作的字节码。 考虑一个示例类:

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

    现在运行这个类的反汇编程序,我们得到:

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

    现在Java VM是基于堆栈的,这意味着对于每个操作,数据将被压入堆栈,并且从堆栈中弹出数据以执行操作。 还有另一个数据结构,通常是一个数组来存储局部变量。 局部变量被赋予了ID,它们只是数组的索引。

    让我们看一下main()方法中的助记符:

  • iconst_0 :常量值0被推入堆栈。
  • istore_1 :堆栈的顶层元素被弹出并存储在索引为1的局部变量中
    这是x
  • iload_1 :位置1处的x值为0值被压入堆栈。
  • iinc 1, 1 :存储器位置1值增加1 。 所以x现在变成1
  • istore_1 :堆栈顶部的值被存储到内存位置1 。 这是0分配给x 覆盖其递增的值。
  • 因此, x的值不会改变,导致无限循环。

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

    上一篇: Why does this go into an infinite loop?

    下一篇: Is there a "do ... while" loop in Ruby?