Demo
有下面的一道题目,大家猜一下打印结果是什么
public static void main(String[] args) {
int i = 1;
i = i++;
int j = i++;
int k = i + ++i * i++;
System.out.println(i);
System.out.println(j);
System.out.println(k);
}
结果:
4
1
11
讲解
我们先用通俗的说法给个解释,然后再从指令的角度看看他怎么执行。
通俗解法
++i:先自加后使用,也就是遇到这个语句,你脑海里就直接把这块当成已经加了 1的一个变量。
int i = 0;
int x = ++i; // i 先自加,然后再赋值给 xSystem.out.println(x);//1System.out.println(i);//1
i++:先使用后自加
int i = 0;
int x = i++;// i 先赋值给 x,然后自己再自加System.out.println(x);//0System.out.println(i);//1
按照上面给的原则我们来解一下
int i = 1; // i = 1i = i++; // i ++ 先赋值,再自加,但是这里又赋值给了 i,所以相当于没有自加int j = i++; // 先赋值,j = 1,再自加,执行完 i = 2,int k = i + ++i * i++;// 乘除先运算,先算 ++i = 3,再看 i++,因为前面 i= 3,这里就先利用了 3,然后去自加。所以是 2 + 3 * 3System.out.println(i);
System.out.println(j);
System.out.println(k);
有些人可能会迷糊了,其实这个k = i + ++i * i++可以分解为:
i = 2
m = i; // = 2x = ++i; // x = 3,这里没有疑问,y = i++; // y = 3,但是这里为什么=3?因为上一步,我们的加了之后以及是 3 了,这里先利用,所以 y = 3,// 如果你在这里打印 i,应该是 i = 4k = m + x * y;// 2 + 3 * 3 = 11
指令解法
我们寻根问底,为什么会是这个逻辑呢?而且上面的通俗说法总感觉不可靠,我们就来看看底层实现。
使用 idea 编辑器,查看class 的字节码文件【View->Show Bytecode,需要开启插件】
// class version 52.0 (52)
// access flags 0x21
public class top/ybq87/MainClass {
// compiled from: MainClass.java
// access flags 0x1
public ()V
L0
LINENUMBER 12 L0
ALOAD 0
INVOKESPECIAL java/lang/Object. ()V
RETURN
L1
LOCALVARIABLE this Ltop/ybq87/MainClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 15 L0
ICONST_1
ISTORE 1
L1
LINENUMBER 16 L1
ILOAD 1
IINC 1 1
ISTORE 1
L2
LINENUMBER 17 L2
ILOAD 1
IINC 1 1
ISTORE 2
L3
LINENUMBER 18 L3
ILOAD 1
IINC 1 1
ILOAD 1
ILOAD 1
IINC 1 1
IMUL
IADD
ISTORE 3
L4
LINENUMBER 19 L4
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L5
LINENUMBER 20 L5
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 2
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L6
LINENUMBER 21 L6
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 3
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L7
LINENUMBER 22 L7
RETURN
L8
LOCALVARIABLE args [Ljava/lang/String; L0 L8 0
LOCALVARIABLE i I L1 L8 1
LOCALVARIABLE j I L3 L8 2
LOCALVARIABLE k I L4 L8 3
MAXSTACK = 3
MAXLOCALS = 4
}
局部变量表
最底部,我们看到了 4 个局部变量,最后一列是他们的序号,记住这个。
LOCALVARIABLE args [Ljava/lang/String; L0 L8 0
LOCALVARIABLE i I L1 L8 1
LOCALVARIABLE j I L3 L8 2
LOCALVARIABLE k I L4 L8 3
每行代码对应的指令我们看看是什么意思,对照一下 jvm 指令参考表,网上google 下有。
public static void main(String[] args) {
// 注意方法编译之后,产生了 4 个局部变量【LOCALVARIABLE】,args,i,j,k,序号分别为 0,1,2,3 // 我们用数组的概念来代替下,方便理解,arr[0] = args,arr[1] = i,arr[2] = j,arr[3] = k, 当然实际情况更复杂点 // LOAD、STORE 指令后面的参数,第一个参数一般是局部变量的序号,也就是数组角标 /*查看指令ICONST_1 :将 int 型的值 1 推送至栈顶,操作数栈 = 1ISTORE 1 :将栈顶 int 型数值存入指定序号的局部变量,序号为 1 的局部变量为 i,所以 i = 1,操作数栈全部弹出*/
int i = 1;
/*查看指令ILOAD 1 :将指定的 int 型的序号为 1 的局部变量的值,推送至栈顶,也就是将 i 的值压栈,操作数栈:1// iinc 用于实现局部变量的自增操作。在所有字节码指令中,只有该指令可直接用于操作局部变量。// 也就是说,这个命令不会改变操作数栈!IINC 1 1 :int 型的序号为 1 的局部变量,增加指定值 1, 所以 i = 1 + 1,但是操作数栈:1ISTORE 1 :将栈顶 int 型数值存入指定序号的局部变量;序号为 1 的变量为 i,所以将操作数栈顶针,出栈给 i = 1,操作数栈空*/
i = i++;
/*ILOAD 1 :加载序号为 1 的局部变量的值到操作数栈:1IINC 1 1 :局部变量序号为 1 的值+1,也就是局部变量 i 自己+1,i = 2,但是操作数栈还是 1ISTORE 2 :将栈顶的元素弹栈,将值给需要为 2 的局部变量,也就是 j。j = 1,此时 i 已经是 2 了*/
int j = i++;
/*查看指令:ILOAD 1 :序号 = 1 的局部变量值压栈,操作数栈 = 2IINC 1 1 :序号 = 1 的局部变量自加,i 之前已经是 2了,再自加 = 3,操作数栈不变 = 2ILOAD 1 :将 序号 = 1 的局部变量又压栈,也就是 i = 3,将 3 压栈,操作数栈:3,2ILOAD 1 :再压栈,i = 3,操作数栈:3,3,2IINC 1 1 :i++,此时 i = 4,此操作不影响操作数栈,所以操作数栈:3,3,2IMUL :将栈顶两int型数值相乘并将结果压入栈顶,前 2 个值为 3*3 = 9,将 9 压栈,操作数栈为:9,2IADD :将栈顶两int型数值相加并将结果压入栈顶,9 + 2,压栈,操作数栈:11ISTORE 3 :弹栈,赋值给需要为 3 的局部变量,k = 11*/
int k = i + ++i * i++;
// 猜测打印结果 System.out.println(i);
System.out.println(j);
System.out.println(k);
}
这样是不是很清晰了。