502

我有以下代码:

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

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

- 更新

这是字节码:

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

}

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

4

26 回答 26

360

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

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

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分配给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,我整理了一个快速演示来说明这个中间值确实“存在”,尽管它会永远不会在执行线程上“看到”。

演示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 的不规则出现。

启动后台线程...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1
于 2010-09-30T15:09:25.593 回答
175

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

  • 首先它计算表达式x++。对该表达式的求值产生一个表达式值(即x增量之前的值)和增量x
  • 稍后它将表达式值分配给x,覆盖增量值。

因此,事件序列如下所示(这是一个实际的反编译字节码,由 生成javap -c,带有我的评论):

   8: iload_1 // 记住栈中 x 的当前值
   9: iinc 1, 1 // 增加 x (不改变堆栈)
   12: istore_1 // 将记忆值从堆栈写入 x

为了比较,x = ++x

   8: iinc 1, 1 // 增加 x
   11: iload_1 // 将 x 的值压入堆栈
   12: istore_1 // 从栈中弹出值到 x
于 2010-09-30T14:13:44.087 回答
106

发生这种情况是因为 的值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()

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

于 2010-09-30T14:15:55.407 回答
54
  1. 前缀表示法将在计算表达式之前递增变量。
  2. 后缀符号将在表达式评估之后递增。

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

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

  1. x准备分配(评估)
  2. x递增
  3. x分配给的先前值x
于 2010-09-30T14:27:49.500 回答
34

没有一个答案很明显,所以这里是:

当您编写int x = x++时,您并没有将x其本身赋值为新值,而是将其赋值xx++表达式的返回值。正如Colin Cochrane 的回答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,我们会收到更新后的值,即 1。

于 2010-09-30T14:37:22.957 回答
29

其他人已经解释的很好了。我只包括相关 Java 规范部分的链接。

x = x++ 是一个表达式。Java 将遵循评估顺序。它将首先评估表达式 x++,这将增加 x 并将结果值设置为 x 的前一个值。然后它将表达式结果分配给变量 x。最后,x 又回到了之前的值。

于 2010-09-30T15:33:21.267 回答
18

这个说法:

x = x++;

评估如下:

  1. x入堆栈;
  2. 增量x
  3. x从堆栈中弹出。

所以值不变。将其与以下内容进行比较:

x = ++x;

评估为:

  1. 增量x
  2. x入堆栈;
  3. x从堆栈中弹出。

你想要的是:

while (x < 3) {
  x++;
  System.out.println(x);
}
于 2010-09-30T14:10:20.810 回答
10

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

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

于 2010-10-01T22:18:17.660 回答
8

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

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

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

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

这将打印 1 和 0。

于 2010-09-30T14:27:30.653 回答
8

你真的不需要机器代码来理解发生了什么。

根据定义:

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

    1.1。x 的当前值被复制到这个临时变量中

    1.2. x 现在递增。

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

这很简单。

于 2010-10-01T04:10:05.853 回答
7

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

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

这个想法是后增量运算符(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.
于 2010-09-30T14:16:51.490 回答
5

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

x = 0;

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

于 2010-09-30T14:11:35.433 回答
3

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

于 2010-09-30T14:15:55.877 回答
1

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

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

于 2010-09-30T14:16:20.797 回答
1

这可以按照您对另一个人的期望进行。这是前缀和后缀之间的区别。

int x = 0; 
while (x < 3)    x = (++x);
于 2010-09-30T14:39:12.127 回答
1

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

于 2010-09-30T23:15:11.640 回答
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指令的效果)

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

iinc 1,1
iload_1
istore_1

因此,只需交换前两行,就改变了语义,以便在增量之后留在堆栈顶部的值(即表达式的“值”)是增量之后的值。

于 2010-10-02T01:16:25.080 回答
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

当然,最终的结果与justx++;++x;单独一行相同。

于 2011-11-26T11:08:10.737 回答
0
 x = x++; (increment is overriden by = )

由于上述语句 x 永远不会达到 3;

于 2010-09-30T17:30:43.547 回答
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

应该无关紧要。我认为,任何一种实现都应该是有效的。

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

于 2010-09-30T19:55:18.667 回答
0

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

同样,如果你写 x=x+1...+ 的优先级高于 = (assignment)

于 2010-10-01T07:00:54.023 回答
0

在将值加一之前,将值分配给变量。

于 2010-10-01T07:59:23.980 回答
0

x++表达式的计算结果为x。该++部分影响评估后的价值,而不是声明后。所以x = x++被有效地翻译成

int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment
于 2010-10-01T16:38:27.583 回答
0

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

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

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

在Post Increment的定义中查看更多信息。

于 2010-10-01T17:23:40.380 回答
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表示递增值并返回递增前的值。这就是为什么temp值为0。那么 iftemp = i和 this 在一个循环中怎么办(第一行代码除外)。就像问题一样!!!!

于 2015-05-29T08:31:01.063 回答
-1

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

于 2010-12-10T11:29:09.037 回答