问题引入:
最开始是因为看到了一个题:
① short s1 = 1; s1 = s1 + 1;
② short s1 = 1; s1 += 1;
上面两条是否都可以成功编译?
答:① 中,由于 1 是 int 类型的,因此 s1 + 1
也是 int 类型的,需要进行强制类型转换,才能赋值给 short;
② 中,s1 += 1
就相当于 s1 = ((short)s1 + 1)
,+=
本身就隐含着强制类型转换,故可正确编译。
问题拓展:
上面涉及到了 +=
和 s1 = s1 + 1
的区别,除了 +=
本身隐含强制类型转换以外,+=
的效率也会比 s1 = s1 + 1
高一些。
突然想起了多次看过的 i++
和 ++i
的区别的问题,当时只是大概知道 i++
是用完了再自增,而 ++i
是自增了再用,既然想到了,就从原理上搞搞清楚。
一个 Test 看 i++ 和 ++i:
查资料的过程中看到了一个题,自己去跑了一下:
package test;
public class test {
public static void main(String[] args) {
int i = 1;
i = i++;
System.out.println("i=" + i);
int j = i++;
System.out.println("j=" + j + " i=" + i);
int k = i + ++i * i++;
System.out.println("k=" + k + " i=" + i);
}
}
输出结果:
原理分析:
从《深入理解Java虚拟机(第3版)》里可以看到,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。
而这里会用到的是栈帧中的局部变量表、操作数栈。
对于 i++ 来说,一共需要 2 步:
① 把 i 放到操作数栈里;
② 对局部变量表里的 i 自增。
对于 ++i 来说,与上面相反:
① 对局部变量表里的 i 自增,此时操作数栈为空;
② 把 i 放到操作数栈里。
下面来看 test 里的每个语句对应着发生了什么,首先需要明确:
System.out.println
输出的是局部变量表里的值;
操作数栈,是一个后入先出栈。当一个方法刚刚开始执行的时候,该方法的操作数栈是空的,在方法执行的过程中,会有个各种字节码指令望操作数栈中写入和提取内容,也就是出栈和入栈操作。
赋值操作是将操作数栈中的操作数经过运算后赋值给局部变量表中的变量的。
int i = 1;
此时局部变量表中为 i = 1 ,操作数栈为空。
i = i++;
注意不是 i++,是先 i++ 然后有个赋值操作:
① i 进操作数栈,此时局部变量表 i = 1 ,操作数栈 i = 1;
② 局部变量表中自增,此时局部变量表 i = 2,操作数栈 i = 1;
③ 赋值操作是将操作数栈中的数赋值给 i ,此时局部变量表 i = 1,操作数栈 i = 1。
int j = i++;
跟上面的操作类似,只是最终赋值给了 j :
① ②与上面的相同;
③ 将栈中的 i = 1 赋值给局部变量表中的 j,此时局部变量表 i = 2,j = i(1),操作数栈 i = 1。
int k = i + ++i * i++;
这条语句包含了四个部分,分别是 i
,i++
,++i
,以及最后计算完的赋值操作:
(1) int k = i + ++i * i++;
i 入栈,此时局部变量表 i = 2 ,操作数栈 i = 2;
(2) int k = i + ++i * i++;
①局部变量表中自增,此时局部变量表 i = 3 ,操作数栈 i = 2;
② i 入栈,此时局部变量表 i = 3 ,操作数栈 i = 3, i = 2;
(3) int k = i + ++i * i++
;
① i 入栈,此时局部变量表 i = 3 ,操作数栈 i = 3,i = 3, i = 2;
② 局部变量表中自增 1,此时局部变量表 i = 4,操作数栈 i = 3,i = 3, i = 2;
(4) 赋值给 k
对操作数栈中的操作数进行运算,再赋值给局部变量表中的 k,即 k = 3 * 3 + 2 = 11。
参考资料:
- 《深入理解Java虚拟机(第3版)》
- 公众号文章:终于弄明白 i = i++和 i = ++i 的区别了!