我有以下代码:
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 回答
来自http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html
为了说明,请尝试以下方法:
其中将打印1和0 .
它正在发生,因为它的帖子增加了 . 这意味着在计算表达式后变量会递增 .
x现在是10,但是y是9,x之前的值是递增的 .
在Definition of Post Increment中查看更多信息 .
Note :最初我在此答案中发布了C#代码用于说明目的,因为C#允许您通过
ref
关键字引用传递int
参数 . 我决定使用我在Google上找到的第一个MutableInt类来使用实际的合法Java代码更新它,以便对C#中的ref
进行排序 . 我可以完成所有Java开发工作;所以我知道可能有更多的惯用方法来说明这一点 .也许如果我们写出一个方法来做相当于
x++
的工作,它会使这个更清楚 .对?增加传递的值并返回原始值:这是postincrement运算符的定义 .
现在,让我们看看您的示例代码中如何显示此行为:
postIncrement(x)
做什么?增加x
,是的 . 然后 returns what x was before the increment . 然后将此返回值分配给x
.因此,分配给
x
的值的顺序为0,然后是1,然后是0 .如果我们重写以上内容,这可能会更清楚:
你对
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
使得事情与上面的场景没有什么不同;我们只是得到:所以很明显:
x = x++
实际上不会改变x的值 . 它总是使x的值为x0,然后是x0 1,然后是x0 .Update :顺便说一下,为了避免怀疑
x
曾被分配给1 "between"增量操作和上面例子中的赋值,我把一个快速演示放在一起来说明这个中间值确实"exist,"虽然它永远不会是"seen"执行线程 .演示在循环中调用
x = x++;
,而单独的线程连续将x
的值打印到控制台 .以下是上述程序输出的摘录 . 注意1和0的不规则出现 .
x = x++
以下列方式工作:首先它评估表达式
x++
. 对此表达式的求值会生成一个表达式值(在递增之前为x
的值)并递增x
.稍后它将表达式值赋给
x
,覆盖递增的值 .因此,事件序列如下所示(它是一个实际的反编译字节码,由
javap -c
产生,带有我的注释):为了比较,
x = ++x
:发生这种情况是因为
x
的值根本没有增加 .相当于
说明:
我们来看看这个操作的字节代码 . 考虑一个示例类:
现在运行类反汇编程序,我们得到:
现在Java VM是基于堆栈的,这意味着对于每个操作,数据将被压入堆栈并从堆栈中弹出数据以执行操作 . 还有另一种数据结构,通常是用于存储局部变量的数组 . 局部变量给出id,它们只是数组的索引 .
让我们看一下mnemonics中的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
overwriting 其递增值 .因此
x
的值不会改变,从而导致无限循环 .前缀表示法将在计算表达式之前增加变量BEF .
后缀表示法将在表达式评估后递增 .
但是“
=
" has a lower operator precedence than "++
” .所以
x=x++;
应评估如下x
准备分配(已评估)x
递增x
的前一个值已分配给x
.没有任何答案在哪里,所以这里是:
当你写
int x = x++
时,你并没有指定x
本身处于新值,你是将x
指定为x++
表达式的返回值 . 这恰好是x
的原始值,如Colin Cochrane's answer中暗示的那样 .为了好玩,请测试以下代码:
结果将是
表达式的返回值是
x
的初始值,为零 . 但是稍后,当读取x
的值时,我们会收到更新的值,即一个 .它已经被其他人很好地解释了 . 我只是包含相关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返回其先前的值 .
这个说法:
评估如下:
将
x
推入堆栈;增量
x
;从堆栈中弹出
x
.所以 Value 没有变化 . 比较一下:
评估为:
增量
x
;将
x
推入堆栈;从堆栈中弹出
x
.你想要的是:
答案很简单 . 它与评估事物的顺序有关 .
x++
返回值x
然后递增x
.因此,表达式
x++
的值为0
. 所以你每次都在循环中分配x=0
. 当然x++
会递增此值,但这会在赋值之前发生 .您有效地获得了以下行为 .
抓取x的值(即0)为右侧的"the result"
递增x的值(所以x现在为1)
将右侧的结果(保存为0)分配给x(x现在为0)
这个想法是后增量运算符(x)增加有问题的变量AFTER返回它的值以便在它使用的等式中使用 .
编辑:由于评论而添加一点点 . 考虑如下 .
您真的不需要机器代码来了解正在发生的事情 .
根据定义:
1.1 . x的当前值被复制到此临时变量中
1.2 . x现在递增 .
这很简单 .
这是因为在这种情况下它永远不会增加 .
x++
将在递增之前首先使用它的值,就像这种情况一样,它将像:但如果你做
++x;
,这会增加 .该值保持为0,因为
x++
的值为0.在这种情况下,如果x
的值增加与否则无关紧要,则执行赋值x=0
. 这将覆盖临时递增的x
值(对于"very short time"为1) .这符合您对另一个人的期望 . 这是前缀和后缀之间的区别 .
将x视为一个函数调用"returns"在增量之前是什么X('s why it' s称为后增量) .
所以操作顺序是:
1:在递增之前缓存x的值
2:增量x
3:返回缓存值(x在增加之前)
4:返回值分配给x
如果在rhs上,则在数字递增之前返回结果 . 改为x,一切都会好的 . Java会对此进行优化以执行单个操作(将x分配给x)而不是增量 .
就我所见,由于赋值覆盖递增的值,错误发生在增量之前的值,即它撤消增量 .
具体地,“x”表达式在递增之前具有值“x”而不是在递增之后具有值“x”的“x” .
如果您有兴趣研究字节码,我们将看看有问题的三行:
7:iload_1#将第二个局部变量的值放在堆栈上
8:iinc 1,1#会将第2个局部变量增加1,注意它会离开堆栈未受影响!
9:istore_1#将弹出堆栈顶部并将此元素的值保存到第二个局部变量
(您可以阅读每个JVM指令的效果here)
这就是为什么上面的代码将无限循环,而带x的版本则不会 . x的字节码应该看起来完全不同,据我记得一年多前写的1.3 Java编译器,字节码应该是这样的:
因此,只需交换两个第一行,就会改变语义,使得在增量(即表达式的“值”)之后,堆栈顶部留下的值是增量之后的值 .
所以:
而
所以:
当然最终结果与单独的
x++;
或++x;
相同 .句子
“翻译”为
而已 .
因为上述陈述x永远不会达到3;
我想知道Java规范中是否有任何内容可以准确定义它的行为 . (该陈述的明显含义是我懒得检查 . )
从Tom的字节码注意到,关键行是7,8和11.第7行将x加载到计算堆栈中 . 第8行增加x . 第11行将堆栈中的值存储回x . 在正常情况下,您没有将值分配给自己,我认为没有任何理由您无法加载,存储,然后递增 . 你会得到相同的结果 .
比如,假设你有一个更正常的例子,你写了类似的东西:z =(x)(y);
是否说(伪代码跳过技术细节)
要么
应该是无关紧要的 . 我想,要么实施都应该有效 .
在编写依赖于此行为的代码时,我会非常谨慎 . 它看起来非常依赖于实现,在我之间的规格之间 . 它唯一会产生影响的是你做了一些疯狂的事情,比如这里的例子,或者你有两个线程在运行并依赖于表达式中的评估顺序 .
我认为因为Java中的优先级高于=(赋值)...是吗?看看http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html ......
如果你写x = x 1 ...的方式相同,那么优先级高于=(赋值)
x++
表达式的计算结果为x
.++
部分影响评估后的值,而不是语句后的值 . 所以x = x++
被有效地翻译成了在将值递增1之前,将值分配给变量 .
检查以下代码,
输出将是,
post increment
表示 increment the value and return the value before the increment . 这就是为什么值temp
是0
. 那么如果temp = i
并且这是一个循环(第一行代码除外)该怎么办呢 . 就像在问题!!!!增量运算符应用于您指定的同一变量 . 那是在惹麻烦 . 我确信你可以在运行这个程序时看到你的x变量的值....这应该清楚为什么循环永远不会结束 .