本文主要分析i++、++i和i++ + ++i的结果,这些也是面试题中最常考的,但有的读者可能总是记不住,原因就在于读者未能深入到字节码层面做进一步的研究,本文将从字节码的角度探索此类语法的奥秘。

本文演示案例源码:

int a = 10;
a = a++;

int b = 10;
b = ++b;


System.out.println(a);
System.out.println(b);


int d = 10;
int e = d++ + ++d;
System.out.println(e);

通过javap -c命令,将类的字节码文件反汇编得到如下内容:

Code:
0: bipush 10
2: istore_1
3: iload_1
4: iinc 1, 1
7: istore_1
8: bipush 10
10: istore_2
11: iinc 2, 1
14: iload_2
15: istore_2
16: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
19: iload_1
20: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
23: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
26: iload_2
27: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
30: bipush 10
32: istore_3
33: iload_3
34: iinc 3, 1
37: iinc 3, 1
40: iload_3
41: iadd
42: istore 4
44: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
47: iload 4
49: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
52: return

与本例相关的字节码指令:

指令

作用

bipush

将单字节的常量值推送至栈顶

istore_1

将栈顶int类型数值存入第2个本地变量,同时栈顶的值要出栈

iload_1

将第2个int类型本地变量推送至栈顶

iinc

将指定int类型变量增加指定值(如i++,i--、i+=2)

getstatic

获取指定类的静态字段,并将其值压入栈顶

invokevirtual

调用实例方法

iadd

将栈最前面的两int类型数值相加并将结果压入栈顶

return

从当前方法返回void

先来分析a++,对应汇编代码的第0到第7行。

0: bipush        10   # 将常量10压入栈顶
2: istore_1 # 将栈顶的10赋值给第2个本地变量,即a=10,同时10出栈
3: iload_1 # 将a的值10再次压入栈顶
4: iinc 1, 1 # 第一个1表示是第2个本地变量,第二个1就是数值1,意思是将数值1加到第2个本地变量上,即a=a+1=10+1=11
7: istore_1 # 将栈顶的值赋给第2个本地变量,由于栈顶的值还是10,因此相当于a=10

汇编代码第16到第20行,其实就是打印a,根据上面的分析可知,此时打印的a的值是10。

再来分析++b,对应汇编代码的第8到第15行。

8: bipush        10    # 将常量10压入栈顶
10: istore_2 # 将栈顶的10赋值给第3个本地变量,即b=10,同时10出栈
11: iinc 2, 1 # 2表示第3个本地变量,1就是数值1,意思是将数值1加到第3个本地变量上,即b=b+1=10+1=11
14: iload_2 # 将第3个本地变量的值放到栈顶,此时栈顶的值是11
15: istore_2 # 将栈顶的值存入第3个本地变量,即b=11

汇编代码第23到第27行,其实就是打印b,根据上面的分析可知,此时打印的b的值是11。

根据上面对a++和++b的分析,总结如下:

  • 相同点:最后会将栈顶的值赋给a或b
  • 不同点:++b会将b+1的结果马上置入栈顶,而a++则不会,这也就直接导致了两者最终结果的不同

最后再来分析i++ + ++i,对应汇编代码的第30到第42行。

30: bipush        10   # 将常量10压入栈顶
32: istore_3 # 将栈顶的值存入第4个本地变量,即d=10,同时10出栈
33: iload_3 # 将第4个本地变量的值压入栈顶
34: iinc 3, 1 # 将数值1加到第4个本地变量上,即d=d+1=10+1=11
37: iinc 3, 1 # 将数值1加到第4个本地变量上,即d=d+1=11+1=12
40: iload_3 # 将第4个本地变量的值压入栈顶,此时栈顶的值为12,而随后就是10
41: iadd # 将栈的最前面两个int值相加,即10+12=22,并将结果压入栈顶
42: istore 4 # 将栈顶的值存入第5个本地变量,即e=22

汇编代码第44到第49行,其实就是打印e,根据上面的分析可知,此时打印的e的值是22。

我们一开始在学习C语言的时候就接触到了i++和++i的区别,前者是先用后加,而后者则是先加后用,这是的先用后用说的就是将加1的结果压入栈顶的时机。

对于i++ + ++i来说,假设i=10,那么从前往后分析,i++即将i的值加1,i此时为11,但i++这个表达式的值是10,对于++i,也是将i的值加1,但此时i的值已经是11了,再加1的话是12,且整个表达式的值就是12,那么最终结果就是10+12=22。