为什么这会陷入无限循环?
我有以下代码:
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
的值不会改变,导致无限循环。