|
| 1 | +## 这段代码为什么陷入了死循环 |
| 2 | + |
| 3 | +### 问题 |
| 4 | +我写了这样一段代码 |
| 5 | + |
| 6 | +```java |
| 7 | +public class Tests { |
| 8 | + public static void main(String[] args) throws Exception { |
| 9 | + int x = 0; |
| 10 | + while(x<3) { |
| 11 | + x = x++; |
| 12 | + System.out.println(x); |
| 13 | + } |
| 14 | + } |
| 15 | +} |
| 16 | +``` |
| 17 | + |
| 18 | +我们知道他应该只写x++或者x=x+1就行了,但是x=x++的情况下,x应该先赋值给自己,然后再增加1。为什么X的值一直是0呢? |
| 19 | + |
| 20 | +--更新 |
| 21 | +</br>下面是字节码 |
| 22 | + |
| 23 | +```java |
| 24 | +public class Tests extends java.lang.Object{ |
| 25 | +public Tests(); |
| 26 | + Code: |
| 27 | + 0: aload_0 |
| 28 | + 1: invokespecial #1; //Method java/lang/Object."<init>":()V |
| 29 | + 4: return |
| 30 | + |
| 31 | +public static void main(java.lang.String[]) throws java.lang.Exception; |
| 32 | + Code: |
| 33 | + 0: iconst_0 |
| 34 | + 1: istore_1 |
| 35 | + 2: iload_1 |
| 36 | + 3: iconst_3 |
| 37 | + 4: if_icmpge 22 |
| 38 | + 7: iload_1 |
| 39 | + 8: iinc 1, 1 |
| 40 | + 11: istore_1 |
| 41 | + 12: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; |
| 42 | + 15: iload_1 |
| 43 | + 16: invokevirtual #3; //Method java/io/PrintStream.println:(I)V |
| 44 | + 19: goto 2 |
| 45 | + 22: return |
| 46 | + |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | + |
| 51 | +### 回答 |
| 52 | + |
| 53 | +一开始我用C#代码来解释,因为C#允许用"ref"关键字来实现int参数的引用传递,所以我决定用我google到的MutableInt类写的标准的java代码来模拟C#中ref关键字作用.我不知道这对我的解答是不是有帮助,我并不是一个专业的java开发者,我知道还有很多更符合语言习惯的方法去解释这个问题。 |
| 54 | +也许我写一个函数来模拟x++的作用会解释得更清楚 |
| 55 | + |
| 56 | +```java |
| 57 | +public MutableInt postIncrement(MutableInt x) { |
| 58 | + int valueBeforeIncrement = x.intValue(); |
| 59 | + x.add(1); |
| 60 | + return new MutableInt(valueBeforeIncrement); |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +对不对?将传递的参数值加1然后返回原值,这就是后增操作符的定义。 |
| 65 | +现在让我们看看在你的代码中是怎么执行的。 |
| 66 | + |
| 67 | +```java |
| 68 | +MutableInt x = new MutableInt(); |
| 69 | +x = postIncrement(x); |
| 70 | +``` |
| 71 | + |
| 72 | +`postIncrement(x)`函数做了什么?x增加1,对,然后返回x加1之前的值,并且把该返回值赋给x。所以X被赋值的顺序是0,1,0。如果我们重写上面的代码会理解得更清楚。 |
| 73 | + |
| 74 | +```java |
| 75 | +MutableInt x = new MutableInt(); // x is 0. |
| 76 | +MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0. |
| 77 | +x = temp; // Now x is 0 again. |
| 78 | +``` |
| 79 | + |
| 80 | +你定势地认为,将左边的X换个名称为y,"你能看到x先自增1,然后赋值给了y",这一时也让我有点困惑。bi那个不是x被赋值给y,而是x(自增)之前的值被赋给y.事实上,把左边x变量名改成y结果也没什么不同。 |
| 81 | + |
| 82 | +```java |
| 83 | +MutableInt x = new MutableInt(); // x is 0. |
| 84 | +MutableInt y = new MutableInt(); // y is 0. |
| 85 | +MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0. |
| 86 | +y = temp; // y is still 0. |
| 87 | +``` |
| 88 | + |
| 89 | +可见,x=x++并没有改变x的值,只是让x依次被赋值为x0,x0+1,x0。 |
| 90 | + |
| 91 | +更新:你可能会怀疑在自增过程中x并没有被赋值为1,下面通过一个demo来解释确实x存在赋值为1的过程。 |
| 92 | +demo中调用了x=x++;同时另外开了一个单独的线程不断输出x的值. |
| 93 | + |
| 94 | +```java |
| 95 | +public class Main { |
| 96 | + public static volatile int x = 0; |
| 97 | + |
| 98 | + public static void main(String[] args) { |
| 99 | + LoopingThread t = new LoopingThread(); |
| 100 | + System.out.println("Starting background thread..."); |
| 101 | + t.start(); |
| 102 | + |
| 103 | + while (true) { |
| 104 | + x = x++; |
| 105 | + } |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +class LoopingThread extends Thread { |
| 110 | + public @Override void run() { |
| 111 | + while (true) { |
| 112 | + System.out.println(Main.x); |
| 113 | + } |
| 114 | + } |
| 115 | +} |
| 116 | +``` |
| 117 | + |
| 118 | +下面是程序的部分输出,0和1不规则的出现。 |
| 119 | + |
| 120 | +``` |
| 121 | +Starting background thread... |
| 122 | +0 |
| 123 | +0 |
| 124 | +1 |
| 125 | +1 |
| 126 | +0 |
| 127 | +0 |
| 128 | +0 |
| 129 | +0 |
| 130 | +0 |
| 131 | +0 |
| 132 | +0 |
| 133 | +0 |
| 134 | +0 |
| 135 | +0 |
| 136 | +1 |
| 137 | +0 |
| 138 | +1 |
| 139 | +``` |
| 140 | + |
| 141 | + |
| 142 | +stackoverflow链接: |
| 143 | +[http://stackoverflow.com/questions/3831341/why-does-this-go-into-an-infinite-loop](http://stackoverflow.com/questions/3831341/why-does-this-go-into-an-infinite-loop) |
0 commit comments