为什么会进入无限循环?

时间:2010-09-30 14:07:51

标签: java loops operators variable-assignment increment

我有以下代码:

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作为值?

- 更新

这是字节码:

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试图理解......

26 个答案:

答案 0 :(得分:353)

注意:为了便于说明,我最初在此答案中发布了C#代码,因为C#允许您通过int关键字引用ref参数。我决定使用我在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的{​​{1}}不是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具有值x 0 ,然后x 0 + 1,然后再次x 0


更新:顺便说一下,为免您怀疑x之间的增量操作和分配之间的“{1}}被分配到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

答案 1 :(得分:169)

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 :(得分:104)

这是因为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()方法中的mnemonics

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

答案 3 :(得分:52)

  1. 前缀表示法将在计算表达式之前递增变量。
  2. 后缀表示法将在表达式评估后递增。
  3. 但是“=”的运算符优先级低于“++”。

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

    1. x准备分配(已评估)
    2. x递增
    3. 分配给x的{​​{1}}的上一个值。

答案 4 :(得分:34)

没有一个答案在哪里,所以这里是:

当您撰写int x = x++时,您并未将x指定为新值,而是将x指定为{{的返回值1}}表达。这恰好是x++的原始价值,如Colin Cochrane's answer中暗示的那样。

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

x

结果将是

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

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

答案 5 :(得分:29)

其他人已经很好地解释过了。我只是包含相关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返回其先前的值。

答案 6 :(得分:18)

本声明:

x = x++;

评估如下:

  1. x推入堆栈;
  2. 增量x;
  3. 从堆栈弹出x
  4. 所以价值不变。将其与:

    进行比较
    x = ++x;
    

    评估为:

    1. 增量x;
    2. x推入堆栈;
    3. 从堆栈弹出x
    4. 你想要的是:

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

答案 7 :(得分:10)

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

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

答案 8 :(得分:8)

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

  

增量/减量运算符可以   在(前缀)之前或之后应用   (后缀)操作数。代码   结果++;和++结果;将结束   结果加1。   唯一的区别是前缀   version(++ result)计算结果   递增的值, 而   后缀版本(结果++)进行评估   原始值 。如果你是   只是表演简单   递增/递减,它不是真的   你选择哪个版本。但   如果您在a的一部分中使用此运算符   更大的表达,你的那个   选择可能会有重大意义   差。

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

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

将打印1和0。

答案 9 :(得分:7)

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

  1. 抓取x的值(即0)作为右侧的“结果”
  2. 增加x的值(因此x现在为1)
  3. 将右侧的结果(保存为0)分配给x(x现在为0)
  4. 这个想法是后增量运算符(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.
    

答案 10 :(得分:7)

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

根据定义:

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

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

    1.2。 x现在递增。

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

  3. 这很简单。

答案 11 :(得分:5)

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

x = 0;

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

答案 12 :(得分:3)

该值保持为0,因为x++的值为0.在这种情况下,x的值是否增加无关紧要,赋值x=0为执行。这将覆盖临时递增的x值(“非常短的时间”为1)。

答案 13 :(得分:1)

这可以达到您对另一个人的期望。这是前缀和后缀之间的区别。

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

答案 14 :(得分:1)

据我所知,由于赋值覆盖递增的值,错误发生,增量前的值,即撤消增量。

具体来说,“x ++”表达式在递增之前具有值“x”,而不是“++ x”,在递增之后具有值“x”。

如果您对调查字节码感兴趣,我们将看看有问题的三行:

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

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

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

iinc 1,1
iload_1
istore_1

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

答案 15 :(得分:1)

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

答案 16 :(得分:1)

    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;相同。

答案 17 :(得分:1)

将x ++视为一个函数调用,在增量之前“返回”X是的原因(这就是为什么它被称为后增量)。

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

答案 18 :(得分:0)

检查以下代码,

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

输出将是,

temp = 0
x = 0

post increment表示增加值并返回增量前的值。这就是价值temp0的原因。那么如果temp = i并且这是一个循环(第一行代码除外)该怎么办呢。就像在问题!!!!

答案 19 :(得分:0)

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

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

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

Definition of Post Increment 中查看更多内容。

答案 20 :(得分:0)

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

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

答案 21 :(得分:0)

在将值递增1之前,将值赋值给变量。

答案 22 :(得分:0)

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

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

答案 23 :(得分:0)

我想知道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

应该是无关紧要的。我认为,要么实施都应该有效。

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

答案 24 :(得分:0)

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

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

答案 25 :(得分:-1)

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