【面试】i++与++i的水有多深,你真的了解吗?
i++是先算后加,++i是先加后算,这个在我们学习循环就已经知道的东西,到底蕴含了什么呢?
我们来看一段代码
int i1=10;
int i2=i1++;
int i3=10;
int i4=++i3;
int i5 = 10;
i5 = i5++;
int i6=10;
i6 = ++i6;
int i7=10;
int i8 = i7++ + ++i7;
i7=10;
int i9 = ++i7 + i7++;
稍稍有点眼花缭乱,没关系,很简单,认真看看就能看懂,那么他们输出什么呢?
i1=:11
i2=:10
i3=:11
i4=:11 //直到i4大家都应该懂
i5=:10 // i5以后可能有点难度
i6=:11
i7=:12
i8=:22
i9=:22
要解释i5,i6,i8,i9,需要从java的字节码角度去探索
栈结构
我们知道,java虚拟机中对象一般在堆中,而常量运算一般在栈中(这里是简略描述,真实比这个复杂)
上面的所有运算都是在栈中完成的,栈的基本单位是栈帧,而一个方法所占用的空间就是一个栈帧,上面只有一个main方法,所以占用一个栈帧
如图
每个栈帧对应一个方法,包含五个内容,但是由于我们这道题只涉及局部变量表和操作数栈,我们只看这两个
内容 | 接受 |
---|---|
局部变量表 | 记录方法中变量的值 |
操作数栈 | 记录运算过程中的中间变量 |
局部变量表可以看做是菜谱,操作数栈可以看做是做菜的中间状态
字节码
现在我们知道,我们的代码将会在栈帧中的局部变量表和操作数栈中发生事情,那如何发生?,就是通过字节码指令运行,字节码由java代码编译而来,在idea中安装jclasslib插件即可查看
字节码如下
先了解以下4行代码
0 bipush 10 //将10压入操作数栈
2 istore_1 //将栈顶的数保存到局部变量表的1位置处
3 iload_1 //将局部变量表1位置处的值取出
4 iinc 1 by 1 //将局部变量表中1位置处的值自增1
好,准备工作做完了,现在开始分析
前6个
int i1=10;
int i2=i1++;
int i3=10;
int i4=++i3;
int i5 = 10;
i5 = i5++;
int i6=10;
i6 = ++i6;
字节码如下,字节码前面的数字表示的是地址,不是代码行数
0 bipush 10 //压入10到栈中
2 istore_1 //将栈顶的10保存到局部变量表的1处(简称i1)
3 iload_1 // 复制i1并取出
4 iinc 1 by 1 //自增位于局部变量表中的i1
7 istore_2 //将取出的i1放到局部变量表的2处(简称i2)
//注意,这里是先取出再自增,取出的i1还是原值,所以i2依旧为10
8 bipush 10
10 istore_3
11 iinc 3 by 1 //这里是先自增再取出
14 iload_3
15 istore 4 //i4为10
17 bipush 10
19 istore 5
21 iload 5 //i5先取出
23 iinc 5 by 1 //局部变量表处再自增
26 istore 5 //又将取出的值存回去,所以先取出的10覆盖了自增的11
28 bipush 10
30 istore 6
32 iinc 6 by 1 //i6先自增再取出
35 iload 6
37 istore 6 // 所以取出的是11,覆盖的也是11
最后两个
int i7=10;
int i8 = i7++ + ++i7;
i7=10;
int i9 = ++i7 + i7++;
首先我们明确,自增操作符++的优先级高于 运算符+号
而且是从左到右运算
来看字节码
39 bipush 10
41 istore 7
43 iload 7
45 iinc 7 by 1
48 iinc 7 by 1
51 iload 7
53 iadd
54 istore 8
56 iinc 7 by 1
59 iload 7
61 iload 7
63 iinc 7 by 1
66 iadd
67 istore 9
这就是i7++与++i7的字节码混合,如图
i7++中局部变量表处自增时,由于没有执行=号,所以没有存回去,这个时候,轮到了++i7,++i7执行了自增后,实际上局部变量表中的i7此时已经是12了
所以23行 iload 7的值为10,但是26行iload 7的值为12,综合下来,还是22
至于i9,一样的道理,可以自己探索哦
这道题就像一道门,可以窥探java虚拟机的复杂内容,深入java虚拟机,做一个不止会搬砖的人~