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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3831341/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 05:35:51  来源:igfitidea点击:

Why does this go into an infinite loop?

javaloopsoperatorsvariable-assignmentincrement

提问by Tom Brito

I have the following code:

我有以下代码:

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

We know he should have writen just x++or x=x+1, but on x = x++it should first attribute xto itself, and later increment it. Why does xcontinue with 0as value?

我们知道他应该只写x++or x=x+1,但在x = x++它上面应该首先归因x于它自己,然后再增加它。为什么x继续0作为价值?

--update

- 更新

Here's the bytecode:

这是字节码:

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

}

I'll read about the instructionsto try to understand...

我将阅读有关说明以尝试理解...

采纳答案by Dan Tao

Note: Originally I posted C# code in this answer for purposes of illustration, since C# allows you to pass intparameters by reference with the refkeyword. I've decided to update it with actual legal Java code using the first MutableIntclass I found on Google to sort of approximate what refdoes in C#. I can't really tell if that helps or hurts the answer. I will say that I personally haven't done all that much Java development; so for all I know there could be much more idiomatic ways to illustrate this point.

注意:最初我在此答案中发布了 C# 代码以进行说明,因为 C# 允许您int使用ref关键字通过引用传递参数。我决定使用MutableInt我在 Google 上找到的第一个类来用实际合法的 Java 代码更新它,以近似refC# 中的功能。我真的不知道这是否有助于或伤害了答案。我会说我个人并没有做过那么多的 Java 开发。所以就我所知,可能有更多惯用的方法来说明这一点。



Perhaps if we write out a method to do the equivalent of what x++does it will make this clearer.

也许如果我们写出一个方法来做等效的事情x++,它会更清楚这一点。

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

Right? Increment the value passed and return the original value: that's the definition of the postincrement operator.

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

Now, let's see how this behavior plays out in your example code:

现在,让我们看看这种行为如何在您的示例代码中发挥作用:

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

postIncrement(x)does what? Increments x, yes. And then returns what xwasbefore the increment. This return value then gets assigned to x.

postIncrement(x)做什么?增量x,是的。然后返回什么x增量之前。然后将此返回值分配给x.

So the order of values assigned to xis 0, then 1, then 0.

因此分配给的值的顺序x是 0,然后是 1,然后是 0。

This might be clearer still if we re-write the above:

如果我们重写上面的内容,这可能会更清楚:

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.

Your fixation on the fact that when you replace xon the left side of the above assignment with y, "you can see that it first increments x, and later attributes it to y" strikes me as confused. It is not xthat is being assigned to y; it is the value formerly assigned to x. Really, injecting ymakes things no different from the scenario above; we've simply got:

当您将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.

So it's clear: x = x++effectively does not change the value of x. It always causes x to have the values x0, then x0+ 1, and then x0again.

所以很明显:x = x++有效地不会改变 x 的值。它总是使 x 具有值 x 0,然后是 x 0+ 1,然后又是 x 0



Update: Incidentally, lest you doubt that xever gets assigned to 1 "between" the increment operation and the assignment in the example above, I've thrown together a quick demo to illustrate that this intermediate value does indeed "exist," though it will never be "seen" on the executing thread.

更新:顺便说一句,为了避免您怀疑x在上面的示例中的增量操作和赋值之间“之间”被赋值为 1,我拼凑了一个快速演示来说明这个中间值确实“存在”,尽管它会永远不会在正在执行的线程上“看到”。

The demo calls x = x++;in a loop while a separate thread continuously prints the value of xto the console.

该演示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);
        }
    }
}

Below is an excerpt of the above program's output. Notice the irregular occurrence of both 1s and 0s.

以下是上述程序输出的摘录。请注意 1 和 0 的不规则出现。

Starting background thread...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1

回答by cletus

This statement:

这个说法:

x = x++;

evaluates like this:

评价如下:

  1. Push xonto the stack;
  2. Increment x;
  3. Pop xfrom the stack.
  1. x入堆栈;
  2. 增量x
  3. x从堆栈中弹出。

So the value is unchanged. Compare that to:

所以值不变。比较一下:

x = ++x;

which evaluates as:

其评估为:

  1. Increment x;
  2. Push xonto the stack;
  3. Pop xfrom the stack.
  1. 增量x
  2. x入堆栈;
  3. x从堆栈中弹出。

What you want is:

你想要的是:

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

回答by mezzie

This is because it never gets incremented in this case. x++will use the value of it first before incrementing like on this case it will be like:

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

x = 0;

But if you do ++x;this will increase.

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

回答by axtavt

x = x++works in the following way:

x = x++工作方式如下:

  • First it evaluates expression x++. Evaluation of this expression produces an expression value (which is the value of xbefore increment) and increments x.
  • Later it assigns the expression value to x, overwriting incremented value.
  • 首先它评估 expression x++。对这个表达式的求值产生一个表达式值(它是x增量前的值)和增量x
  • 稍后它将表达式值分配给x,覆盖递增的值。

So, the sequence of events looks like follows (it's an actual decompiled bytecode, as produced by javap -c, with my comments):

因此,事件序列如下所示(它是一个实际的反编译字节码,由 生成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

For comparison, x = ++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

回答by codaddict

This happens because the value of xdoesn't get incremented at all.

发生这种情况是因为 的值x根本没有增加。

x = x++;

is equivalent to

相当于

int temp = x;
x++;
x = temp;

Explanation:

解释:

Let's look at the byte code for this operation. Consider a sample class:

让我们看看这个操作的字节码。考虑一个示例类:

class test {
    public static void main(String[] args) {
        int i=0;
        i=i++;
    }
}

Now running the class disassembler on this we get:

现在运行类反汇编器,我们得到:

$ 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
}

Now the Java VMis stack based which means for each operation, the data will be pushed onto the stack and from the stack, the data will pop out to perform the operation. There is also another data structure, typically an array to store the local variables. The local variables are given ids which are just the indexes to the array.

现在Java VM是基于堆栈的,这意味着对于每个操作,数据将被推入堆栈,并从堆栈中弹出数据以执行操作。还有另一种数据结构,通常是一个数组来存储局部变量。局部变量被赋予 id,它们只是数组的索引。

Let us look at the mnemonicsin main()method:

让我们来看看助记符main()方法:

  • iconst_0: The constant value 0is pushed on to the stack.
  • istore_1: The top element of the stack is popped out and stored in the local variable with index 1
    which is x.
  • iload_1: The value at the location 1that is the value of xwhich is 0, is pushed into the stack.
  • iinc 1, 1: The value at the memory location 1is incremented by 1. So xnow becomes 1.
  • istore_1: The value at the top of the stack is stored to the memory location1. That is 0is assigned to xoverwritingits incremented value.
  • iconst_0:常量值0被压入堆栈。
  • istore_1: 栈顶元素被弹出并存储在索引1
    为的局部变量 中x
  • iload_1: 所在位置1的值x0, 被压入堆栈。
  • iinc 1, 1: 内存位置的1值增加1。所以x现在变成 1.
  • istore_1:堆栈顶部的值存储到内存位置1。这被0分配为x覆盖其递增值。

Hence the value of xdoes not change resulting in the infinite loop.

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

回答by Progman

The value stays at 0 because the value of x++is 0. In this case it doesn't matter if the value of xis increased or not, the assignment x=0is executed. This will overwrite the temporary incremented value of x(which was 1 for a "very short time").

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

回答by jhabbott

Think of x++ as a function call that "returns" what X was beforethe increment (that's why it's called a post-increment).

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

So the operation order is:
1: cache the value of x before incrementing
2: increment x
3: return the cached value (x before it was incremented)
4: return value is assigned to x

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

回答by RHSeeger

You're effectively getting the following behavior.

您正在有效地获得以下行为。

  1. grab the value of x (which is 0) as "the result" of the right side
  2. increment the value of x (so x is now 1)
  3. assign the result of the right side (which was saved as 0) to x (x is now 0)
  1. 获取 x 的值(即 0)作为右侧的“结果”
  2. 增加 x 的值(所以 x 现在是 1)
  3. 将右侧的结果(已保存为 0)分配给 x(x 现在为 0)

The idea being that the post-increment operator (x++) increments that variable in question AFTER returning its value for use in the equation it's used in.

这个想法是,后增量运算符 (x++) 在返回其值以用于它所使用的等式后递增该变量。

Edit: Adding a slight bit because of the comment. Consider it like the following.

编辑:由于评论而添加一点。像下面这样考虑它。

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.

回答by Colin Cochrane

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

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

The increment/decrement operators can be applied before (prefix) or after (postfix) the operand. The code result++; and ++result; will both end in result being incremented by one. The only difference is that the prefix version (++result) evaluates to the incremented value, whereas the postfix version (result++) evaluates to the original value. If you are just performing a simple increment/decrement, it doesn't really matter which version you choose. But if you use this operator in part of a larger expression, the one that you choose may make a significant difference.

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

To illustrate, try the following:

为了说明,请尝试以下操作:

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

Which will print 1 and 0.

这将打印 1 和 0。

回答by Jaydee

  1. Prefix notation will increment the variable BEFORE the expression is evaluated.
  2. Postfix notation will increment AFTER the expression evaluation.
  1. 前缀表示法将在计算表达式之前增加变量。
  2. 后缀表示法将在表达式求值后递增。

However "=" has a lower operator precedence than "++".

但是,“ =”的运算符优先级低于“ ++”。

So x=x++;should evaluate as follows

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

  1. xprepared for assignment (evaluated)
  2. xincremented
  3. Previous value of xassigned to x.
  1. x准备分配(评估)
  2. x递增
  3. x分配给 的先前值x

回答by Robert Munteanu

None of the answers where quite spot on, so here goes:

没有一个答案很准确,所以这里是:

When you're writing int x = x++, you're not assigning xto be itself at the new value, you're assigning xto be the return value of the x++expression. Which happens to be the original value of x, as hinted in Colin Cochrane's answer.

在编写 时int x = x++,您不是将x自己分配给新值,而是将其分配xx++表达式的返回值。x正如Colin Cochrane 的回答所暗示的那样,这恰好是 的原始值。

For fun, test the following code:

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

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

The result will be

结果将是

0
1

The return value of the expression is the initial value of x, which is zero. But later on, when reading the value of x, we receive the updated value , that is one.

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