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

我有以下代码:

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

}

我会读到关于instructions以试图理解......

回答(27)

2 years ago

Note :最初我在此答案中发布了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 ,是的 . 然后 returns what x was before the increment . 然后将此返回值分配给 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.

你对 y ,"you can see that it first increments x, and later attributes it to y"替换上述作业左侧的 x 这一事实感到困惑 . 它不是 x 被分配给 y ;它是 the value formerly assigned to 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 .


Update :顺便说一下,为了避免怀疑 x 曾被分配给1 "between"增量操作和上面例子中的赋值,我把一个快速演示放在一起来说明这个中间值确实"exist,"虽然它永远不会是"seen"执行线程 .

演示在循环中调用 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

2 years ago

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

2 years ago

发生这种情况是因为 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,它们只是数组的索引 .

让我们看一下mnemonics中的mnemonics方法:

  • iconst_0 :常量值 0 被推入堆栈 .

  • istore_1 :弹出堆栈的顶部元素,并将其存储在索引 1 的局部变量中
    这是 x .

  • iload_1 :位于 1x 的值 0 的值被推入堆栈 .

  • iinc 1, 1 :内存位置 1 的值增加 1 . 所以 x 现在变成 1 .

  • istore_1 :堆栈顶部的值存储在内存位置 1 . 即 0 被赋予 x overwriting 其递增值 .

因此 x 的值不会改变,从而导致无限循环 .

2 years ago

  • 前缀表示法将在计算表达式之前增加变量BEF .

  • 后缀表示法将在表达式评估后递增 .

但是“ = " has a lower operator precedence than " ++ ” .

所以 x=x++; 应评估如下

  • x 准备分配(已评估)

  • x 递增

  • x 的前一个值已分配给 x .

2 years ago

没有任何答案在哪里,所以这里是:

当你写 int x = x++ 时,你并没有指定 x 本身处于新值,你是将 x 指定为 x++ 表达式的返回值 . 这恰好是 x 的原始值,如Colin Cochrane's answer中暗示的那样 .

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

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

结果将是

0
1

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

2 years ago

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

x = x是一个表达式 . Java将遵循evaluation order . 它将首先评估表达式x,其中will increment x and set result value to the previous value of x . 然后它将assign the expression result变为x . 最后,x返回其先前的值 .

2 years ago

这个说法:

x = x++;

评估如下:

  • x 推入堆栈;

  • 增量 x ;

  • 从堆栈中弹出 x .

所以 Value 没有变化 . 比较一下:

x = ++x;

评估为:

  • 增量 x ;

  • x 推入堆栈;

  • 从堆栈中弹出 x .

你想要的是:

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

2 years ago

答案很简单 . 它与评估事物的顺序有关 . x++ 返回值 x 然后递增 x .

因此,表达式 x++ 的值为 0 . 所以你每次都在循环中分配 x=0 . 当然 x++ 会递增此值,但这会在赋值之前发生 .

2 years ago

来自http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html

可以在操作数之前(前缀)或之后(后缀)应用递增/递减运算符 . 代码结果;和结果;将两个结果都加1 . 唯一的区别是前缀版本(结果)评估为增量值,而后缀版本(结果)评估为原始值 . 如果您只是执行简单的增量/减量,那么选择哪个版本并不重要 . 但是,如果您在较大的表达式中使用此运算符,则您选择的运算符可能会产生显着差异 .

为了说明,请尝试以下方法:

int x = 0;
    int y = 0;
    y = x++;
    System.out.println(x);
    System.out.println(y);

其中将打印1和0 .

2 years ago

您有效地获得了以下行为 .

  • 抓取x的值(即0)为右侧的"the result"

  • 递增x的值(所以x现在为1)

  • 将右侧的结果(保存为0)分配给x(x现在为0)

这个想法是后增量运算符(x)增加有问题的变量AFTER返回它的值以便在它使用的等式中使用 .

编辑:由于评论而添加一点点 . 考虑如下 .

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.

2 years ago

您真的不需要机器代码来了解正在发生的事情 .

根据定义:

  • 赋值运算符计算右侧表达式,并将其存储在临时变量中 .

1.1 . x的当前值被复制到此临时变量中

1.2 . x现在递增 .

  • 然后将临时变量复制到表达式的左侧,这是偶然的x!这就是x的旧值再次复制到自身的原因 .

这很简单 .

2 years ago

这是因为在这种情况下它永远不会增加 . x++ 将在递增之前首先使用它的值,就像这种情况一样,它将像:

x = 0;

但如果你做 ++x; ,这会增加 .

2 years ago

该值保持为0,因为 x++ 的值为0.在这种情况下,如果 x 的值增加与否则无关紧要,则执行赋值 x=0 . 这将覆盖临时递增的 x 值(对于"very short time"为1) .

2 years ago

这符合您对另一个人的期望 . 这是前缀和后缀之间的区别 .

int x = 0; 
while (x < 3)    x = (++x);

2 years ago

将x视为一个函数调用"returns"在增量之前是什么X('s why it' s称为后增量) .

所以操作顺序是:
1:在递增之前缓存x的值
2:增量x
3:返回缓存值(x在增加之前)
4:返回值分配给x

2 years ago

如果在rhs上,则在数字递增之前返回结果 . 改为x,一切都会好的 . Java会对此进行优化以执行单个操作(将x分配给x)而不是增量 .

2 years ago

就我所见,由于赋值覆盖递增的值,错误发生在增量之前的值,即它撤消增量 .

具体地,“x”表达式在递增之前具有值“x”而不是在递增之后具有值“x”的“x” .

如果您有兴趣研究字节码,我们将看看有问题的三行:

7:   iload_1
 8:   iinc    1, 1
11:  istore_1

7:iload_1#将第二个局部变量的值放在堆栈上
8:iinc 1,1#会将第2个局部变量增加1,注意它会离开堆栈未受影响!
9:istore_1#将弹出堆栈顶部并将此元素的值保存到第二个局部变量
(您可以阅读每个JVM指令的效果here

这就是为什么上面的代码将无限循环,而带x的版本则不会 . x的字节码应该看起来完全不同,据我记得一年多前写的1.3 Java编译器,字节码应该是这样的:

iinc 1,1
iload_1
istore_1

因此,只需交换两个第一行,就会改变语义,使得在增量(即表达式的“值”)之后,堆栈顶部留下的值是增量之后的值 .

2 years ago

x++
=: (x = x + 1) - 1

所以:

x = x++;
=> x = ((x = x + 1) - 1)
=> x = ((x + 1) - 1)
=> x = x; // Doesn't modify x!

++x
=: x = x + 1

所以:

x = ++x;
=> x = (x = x + 1)
=> x = x + 1; // Increments x

当然最终结果与单独的 x++;++x; 相同 .

2 years ago

句子

x = x++;

“翻译”为

x = x;
x = x + 1;

而已 .

2 years ago

x = x++; (increment is overriden by = )

因为上述陈述x永远不会达到3;

2 years ago

我想知道Java规范中是否有任何内容可以准确定义它的行为 . (该陈述的明显含义是我懒得检查 . )

从Tom的字节码注意到,关键行是7,8和11.第7行将x加载到计算堆栈中 . 第8行增加x . 第11行将堆栈中的值存储回x . 在正常情况下,您没有将值分配给自己,我认为没有任何理由您无法加载,存储,然后递增 . 你会得到相同的结果 .

比如,假设你有一个更正常的例子,你写了类似的东西:z =(x)(y);

是否说(伪代码跳过技术细节)

load x
increment x
add y
increment y
store x+y to z

要么

load x
add y
store x+y to z
increment x
increment y

应该是无关紧要的 . 我想,要么实施都应该有效 .

在编写依赖于此行为的代码时,我会非常谨慎 . 它看起来非常依赖于实现,在我之间的规格之间 . 它唯一会产生影响的是你做了一些疯狂的事情,比如这里的例子,或者你有两个线程在运行并依赖于表达式中的评估顺序 .

2 years ago

我认为因为Java中的优先级高于=(赋值)...是吗?看看http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html ......

如果你写x = x 1 ...的方式相同,那么优先级高于=(赋值)

2 years ago

x++ 表达式的计算结果为 x . ++ 部分影响评估后的值,而不是语句后的值 . 所以 x = x++ 被有效地翻译成了

int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment

2 years ago

在将值递增1之前,将值分配给变量 .

2 years ago

它正在发生,因为它的帖子增加了 . 这意味着在计算表达式后变量会递增 .

int x = 9;
int y = x++;

x现在是10,但是y是9,x之前的值是递增的 .

Definition of Post Increment中查看更多信息 .

2 years ago

检查以下代码,

int x=0;
    int temp=x++;
    System.out.println("temp = "+temp);
    x = temp;
    System.out.println("x = "+x);

输出将是,

temp = 0
x = 0

post increment 表示 increment the value and return the value before the increment . 这就是为什么值 temp0 . 那么如果 temp = i 并且这是一个循环(第一行代码除外)该怎么办呢 . 就像在问题!!!!

2 years ago

增量运算符应用于您指定的同一变量 . 那是在惹麻烦 . 我确信你可以在运行这个程序时看到你的x变量的值....这应该清楚为什么循环永远不会结束 .