我们都知道,i++是先取值后自增,++i是先自增后取值,但是项目这个题目你真的会吗?
下面是看某培训机构分析的视频中一道面试题,请问i,j,k的值一次为多少?
public class Increment {
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(我和我的小伙伴都惊呆了,自己的答案一个都不对啊.......)
下面是自己再看完视频以及查阅资料后的相关整理,要从JVM层面去分析需要先了解一下相应的JVM知识
JVM相关知识
- JVM(HotSpot)的运行时数据区可以分为程序计数器、Java虚拟机栈+本地方法栈(HotSpot把这两个合二为一,这个是我们这里的重点)、Java堆、方法区、运行时常量池。直接内存等。
- JVM是基于栈(这里就是指Java虚拟机栈)的执行引擎,栈中存在许多栈帧(一个方法对应一个栈帧,一个栈帧的入栈到出栈对应一个方法的执行到结束)
- 栈帧中存储了方法的局部变量表、操作数栈、 动态连接、方法返回地址
- 局部变量表中可以简单的理解问数组,其中存储了方法中定义的局部变量,如果是基本数据类型变量,则变量中存放的就是值,如果是引用数据类型变量,其中存放的是值在堆中的地址
- 操作数栈是典型的栈结构(后进先出),采用压栈与出栈操作,主要用于辅助指令的执行
- 自增 自减操作都是直接修改变量的值,不需要经过操作数栈,操作数栈中仅仅保存临时结果
- 字节码指令不管执行顺序如何,都是=右边从左往右加载进操作数栈中
分析开始
首先使用javap -c Increment .class得到程序的反编译文件
Compiled from "Increment.java"
public class com.zkq.test.Increment {
public com.zkq.test.Increment();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
//这里开始是main方法的字节码文件
public static void main(java.lang.String[]);
Code:
/**
* 编号0-1是int i=1的字节码指令
*
*/
//把int型变量1推送到操作数栈的栈顶
0: iconst_1
//把栈顶int型数值(1)存入局部变量表中的第2个本地变量(第一个本地变量用于保存this)
1: istore_1
/**
* 编号2 3 6是i=i++的字节码指令,从执行上可以看出来i的值是1
*
*/
//把局部变量表中的第二个本地变量(int 类型的1)推送到栈顶
2: iload_1
//对局部变量表中i的值进行+1操作,i的值变为2(也就是自增操作)
3: iinc 1, 1
//把栈顶int型数值(1)存入局部变量表中的第2个本地变量(也就是i的位置)
6: istore_1
/**
* 编号7 8 12是int j=i++;的字节码指令,这系列指令与i++的指令很类似
*
*/
//把局部变量表中的第二个本地变量(int 类型的1)推送到栈顶
7: iload_1
//对局部变量表中i的值进行+1操作,i的值变为2(也就是自增操作)
8: iinc 1, 1
//把栈顶int型数值(1)存入局部变量表中的第3个本地变量(也就是j的位置),经过这步,j的值为1,i的值变为2
11: istore_2
/**
* 编号7 8是int k=i+ ++i *i++;的字节码指令,虽然=右边的先执行*操作,但是是按照从左到右依次加载进操作数栈中
*
*/
//这里是把i加载进操作数栈中
//经过12后 ,此时操作数栈中从栈顶到栈底依次为 2
12: iload_1
//13 16是把++i加载进操作数栈中,因为是++i,所有先执行13的自增(自增后i的值变为3),后加载i到操作数栈顶
//经过13 16后,此时操作数栈中从栈顶到栈底元素依次为3 2
13: iinc 1, 1
16: iload_1
//17 18是把i++加载进操作数栈中,因为是i++,所有先把i(3)加载到操作数栈顶,然后对局部变量表中i的值自增1
//经过17 18后,此时操作数栈中从栈顶到栈底元素依次为3 3 2
17: iload_1
18: iinc 1, 1
//把栈顶的两个元素弹出进行乘法运算即3*3=9,然后重新把运算结构压回栈顶
//经过21后,此时操作数栈中从栈顶到栈底元素依次为9 2
21: imul
//把栈顶的两个元素出栈,进行加法操作即9+2=11,然后把运算结构重新压回栈顶
//经过21后,此时操作数栈中只有一个元素为11
22: iadd
//把栈顶int型数值(11)存入局部变量表中的第4个本地变量(也就是k的位置)
23: istore_3
//下面都是一个输出语句
24: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
27: iload_1
28: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
31: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
34: iload_2
35: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
38: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
41: iload_3
42: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
45: return
}