Java:i++问题(详细分析)
2015-09-26 22:30
411 查看
i++问题引发的知识链
一个表面上简单的i++问题,让我从此带着敬畏之心去学习!例子
public class SumPlusTest { public static void main(String[] args){ int a = 1, b = 1, c = 1, d =1; a++; ++b; c = c++; d = ++d; System.out.println("a : " + a + "\nb : " + b + "\nc : " + c + "\nd : " + d); } }
输出结果
a : 2 b : 2 c : 1 d : 2
分析
a、b、d三个参数的结果都没什么问题,暂且放一边,主要问题出来c上,为什么结果是1,而不是2?有两种解释方法,其实最后本质是一样的。
解法一
因为Java用了中间缓存变量机制,所有c=c++可换成如下写法:tmp = c; c = c++; c = tmp;
看着其实比较明确了,但是说实话其实没看懂,什么是中间缓存变量机制,为什么要有这个,官方文档在哪?
这些问题不是很明了,国外的权威资料暂时也没得求证,所以索性从字节码的角度来理解这个问题。
解法二
通过javap工具查看虚指令,通过虚指令理解c=c++到底做了什么。
下面结果只保留部分主要内容来说明
c=c++和其它几种情况的不同,直接翻译虚指令对应程序中的具体操作。
[root@z1 classdir]# javap -verbose SumPlusTest Classfile /mnt/workspace/java/i++issue/classdir/SumPlusTest.class Last modified Sep 3, 2015; size 794 bytes MD5 checksum 3a406dc81fe69722d53ce74ef96516a4 public class SumPlusTest minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #14.#30 // java/lang/Object."<init>":()V #2 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream; ...... #54 = Utf8 println #55 = Utf8 (Ljava/lang/String;)V { public SumPlusTest(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LSumPlusTest; public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=5, args_size=1 0: iconst_1 1: istore_1 #0,1相当于是执行a=1 2: iconst_1 3: istore_2 #2,3相当于是执行b=1 4: iconst_1 5: istore_3 #4,5相当于是执行c=1 6: iconst_1 7: istore_4 #6,7相当于是执行d=1 9: iinc 1, 1 #相当于执行a的值+1 12: iinc 2, 1 #相当于执行b的值+1 15: iload_3 #将c的值放入栈顶 16: iinc 3, 1 #执行c的值+1 19: istore_3 #再将栈顶的值赋值给c,此时即c=1 20: iinc 4, 1 #相当于是执行d的值+1 23: iload 4 #把d的值放置于栈顶 25: istore 4 #然后将栈顶的值付给d这个变量,此时d=2 ...... LocalVariableTable: Start Length Slot Name Signature 0 81 0 args [Ljava/lang/String; 2 79 1 a I 4 77 2 b I 6 75 3 c I 9 72 4 d I }
i++问题例子补充
我承认我钻牛角尖了,测试了几个更奇葩的例子。例一
程序
int i = 0; i = (i++) + (++i); System.out.println("i : " + i);
结果
i=2
分析
0: iconst_0 1: istore_1 2: iload_1 #将数值0推入栈顶 3: iinc 1, 1 6: iinc 1, 1 ##自加两次 9: iload_1 #将数值2推入栈顶 10: iadd #将0和2相加,并将结果放入栈顶 11: istore_1 将i赋值为2
那么我就可以理解为第一个括号里的i++,其实相当于是一个
tmp1=i++的操作,同之前的例子一样,先将i推入栈顶,然后i自加。
第二个括号里的++i,i指向的是和前一个i的同一个slot,因此在原有的基础上继续自加,也就是变成了i=2。
由目前掌握的知识暂时得到一个结论,
++在前则先自增后推入栈,
++在后,则先推入栈顶,后自增。
例二
程序
int i = 0; i = (i++) + (++i) + (i++); System.out.println("i : " + i);
结果
先由已知的知识来推测执行过程:先将i赋值为0,然后将该slot值推入栈顶,栈顶值为0
i进行两次自加,此时slot值为2,推入栈顶,栈顶值为2
栈顶前两个int相加,结果存入栈顶,栈顶值为2
再将slot值推入栈顶,栈顶值为2
注意:slot的值自增1,但是没有推入栈顶。
栈顶前两个int相加,结果存入栈顶,栈顶值为4
结果是i值为4。
然后看实际是怎么操作的。
分析
0: iconst_0 1: istore_1 2: iload_1 3: iinc 1, 1 6: iinc 1, 1 9: iload_1 10: iadd 11: iload_1 12: iinc 1, 1 15: iadd 16: istore_1
整个执行的过程,基本和上面描述的一致。最后输出结果就是4。
例三
程序
再测试一个例子int i = 0; i = (++i) + (i++) + (++i); System.out.println("i : " + i);
结果
再次根据已知的知识来推测执行过程:先将i赋值为0,i自增1,然后推入栈顶,此时slot值为1,栈顶值为1
将slot值继续推入栈顶,此时slot值为1,栈顶值为1
栈顶前两个int相加,结果存入栈顶,此时slot值为1,栈顶值为2
i指向的slot值自增1,此时slot值为2,栈顶值为2
i指向的slot值自增1,此时slot值为3,栈顶值为2
将slot值推入栈顶,并且栈顶前两个int相加,结果存入栈顶,此时slot值为3,栈顶值为5
最后结果即为5
分析
0: iconst_0 1: istore_1 2: iinc 1, 1 5: iload_1 6: iload_1 7: iinc 1, 1 10: iadd 11: iinc 1, 1 14: iload_1 15: iadd 16: istore_1
从javap的结果可以看出来,有一个顺序错了,也就是第3,4两步的顺序需要颠倒一下,页就是说在程序里面第二个括号里面的
i++,先执行自加,然后栈顶的前两个元素才会相加。不过这个顺序小变化不影响最后结果。
总结
通过上面分析可以看出来,一个比较简单的i++问题,如果稍微深入一点学习的话,会涉及到不少平常不注意的内容。对这一个知识点来说,为什么Java会这样来处理不太清楚,但是从结果来反推的话可以得到几个结论:
++在前,则先自增后推入栈;
++在后,则先推入栈顶,后自增
如果
++操作是针对同一个变量,那么不管是在前还是在后,其自增的值都会对后面的操作起到影响。
疑问
那么还是一些疑问。即使通过虚指令明白了
c = c++的与众不同,但是还是不明白为什么会出现这种结果,Java在区分
c = c++和
d = ++d不同的时候是根据表达式的优先级来却别的吗?
还有就是相关的官方权威的资料一直没有找到。
再有一点就是,经过c++代码的测试,
i = 0; i = i++最后i的值是1,这点和Java不同。
相关文章推荐
- java正则表达式积累
- Java输出一个对象
- Java 常用正则表达式
- Spring(六):Bean 的作用域
- 关于java中除0的问题
- 搭建JavaWeb项目时出现的问题
- 浅识异常
- Spring-声明式事务控制
- Java课程作业1
- 为什么eclipse改错后还是显示错误
- getResource()与decode()
- java并发编程学习:用 Semaphore (信号量)控制并发资源
- Java:多个数求和
- SpringMVC 流程(6)-- 常用注解
- 如何将java代码生成一个bat文件
- java程序优化
- Android Eclipse快捷键
- Java 静态代理和动态代理
- java静态代理和动态代理
- Java大数类