java - 为什么进入一个无限循环?

  显示原文与译文双语对照的内容

我是一位老师,昨天一个学生写了以下代码:


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

我们知道他应该有 writen x++ 或者 x=x+1,但是在 x = 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

}

我将阅读关于指令的说明,以便理解。

时间:

随着 ref keyword, 注意: 最初 C# 我所发布的代码在这个答案,或者只是为了方便说明,因为 C# 使你能够传递 int 参数在仅供参考 我已经决定将它的更新与实际法律Java代码中使用第一种 MutableInt 类我谷歌来排序的近似上发现什么 ref does. 我无法确定这是否有帮助或者影响答案。 我就说我个人可以没有这样做所有那么多Java开发;因此对于我只知道那里可以比现在更惯用的方法来说明这一点。


也许如果我们写出一个方法要做的相当于什么 x++ 执行作业会使这更加清晰。


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

右递增传递的值并返回原始值:? 这就是后递增运算符的定义。

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


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

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

分配给 x的值的顺序是 0,然后是 1,然后 0.

这可能会更清楚,如果我们 re-write:


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.

这一事实,当你用 y 替换上述赋值语句左侧的x,"你可以看到它首先递增x,然后将它的属性添加到y"击我,混淆你固定。 这不是正被分配给的xy ;它是该值以前分配给 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 具有值x 0 ,然后是x 0 + 1,然后是x 0 再次。


在执行 thread, 更新: 顺便说一下,以免你怀疑 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虚拟机是基于栈的,这意味着对于每个操作,数据将被推送到堆栈上,从堆栈中弹出数据来执行操作。 还有另一个数据结构,通常是一个用来存储局部变量的数组。 局部变量得到了 ids,它们只是数组的索引。

main() method,让我们看一下记忆法 :

  • 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的值不会改变,导致无限循环。

  1. 前缀表示法将在计算表达式之前增加变量。
  2. 后缀表示法将在表达式求值后递增。

" ="的运算符优先级比" ++"低。

因此 x=x++; 应该按如下方式评估

  1. 为赋值准备的x ( 求值)
  2. x 递增
  3. x 上一个分配给 x的值。

所有的答案都不存在,所以这里:

当你正在写作时 int x = x++ ,你没有将 x 分配给它本身,而是分配 x 作为 x++ 表达式的返回值。 在科林回答的阁麟,作为 hinted. x,它正好是原始值,

为了好玩,测试以下代码:


public class Autoincrement {
 public static void main(String[] args) {
 int x = 0;
 System.out.println(x++);
 System.out.println(x);
 }
}

结果将是

 
0
1

 

表达式的返回值是 x的初始值,该值为零。 但稍后,当读取 x的值时,我们会收到更新的值,即。

它已经被其他人很好地解释了。 我只包含相关的Java规范节的链接。

x = x++是一个表达式。 Java将遵循评估顺序 。 它首先计算表达式 x++,将递增x,并将结果值设置为x的前一个值。 那么它将 将表达式分配给结果到变量x 。 最后,x 回到原来的值。

这里语句:

 
x = x++;

 

计算如下:

  1. x 推送到堆栈上;
  2. 递增 x
  3. 从堆栈中弹出 x

因此该值不变。 将它的与:

 
x = ++x;

 

评估为:

  1. 递增 x
  2. x 推送到堆栈上;
  3. 从堆栈中弹出 x

你需要的是:


while (x <3) {
 x++;
 System.out.println(x);
}

你实际上正在获得以下行为。

  1. 将( 是 0 )的值作为右侧的"结果"
  2. 增加x的值( 所以现在x 是 1 )
  3. 将右侧( 保存为 0 )的结果分配给x ( 现在是 0 )

post-increment运算符( x++ )的思想是,在返回用于在它的中使用的公式的值之后,将在问题中增加该变量。

编辑:由于注释而稍微增加一点。 像下面这样考虑。


x = 1;//x == 1
x = x++ * 5;
//First, the right hand side of the equation is evaluated.
 ==> x = 1 * 5; 
//x == 2 at this point, as it"gave" the equation its value of 1
//and then gets incremented by 1 to 2.
 ==> x = 5;
//And then that RightHandSide value is assigned to 
//the LeftHandSide variable, leaving x with the value of 5.

...