我有以下代码:
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试图理解......
答案 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)
但是“=
”的运算符优先级低于“++
”。
所以x=x++;
应评估如下
x
准备分配(已评估)x
递增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++;
评估如下:
x
推入堆栈; x
; x
。所以价值不变。将其与:
进行比较x = ++x;
评估为:
x
; x
推入堆栈; x
。你想要的是:
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)
您有效地获得了以下行为。
这个想法是后增量运算符(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。 x的当前值被复制到此临时变量
中1.2。 x现在递增。
然后将临时变量复制到表达式的左侧,这是偶然的x!所以这就是x的旧值再次被复制到自身的原因。
这很简单。
答案 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
表示增加值并返回增量前的值。这就是价值temp
为0
的原因。那么如果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变量的值....这应该清楚为什么循环永远不会结束。