提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、代码1:
public void method6(){
int i = 10;
i++;
}
代码1的字节码:
0 bipush 10
2 istore_1
3 iinc 1 by 1
6 return
用javap -v 类名.class 打开字节码文件,找到method 6 ()方法:
public void method6();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: bipush 10
2: istore_1
3: iinc 1, 1
6: return
LineNumberTable:
line 48: 0
line 49: 3
line 53: 6
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/atguigu/java/ArithmeticTest;
3 4 1 i I
下面我们一行行解析字节码:
-
0 bipush 10 :把 10 放进操作数栈中
-
istore_1:取出操作数栈中的10 放到局部变量表索引为1的位置(因为这个是非静态的方法,局部变量表索引为0 的位置放的是this)
-
iinc 1 by 1:把局部变量表中索引为1的位置加1。
所以这行指令执行完之后,这个索引为1的位置就是11了。
- return:因为是void类型的方法,因此这里直接return (注意void方法也是有返回指令的)
因此代码1 如果打印i的话:
public void method6(){
int i = 10;
i++;
System.out.println(i);//10
}
0 bipush 10
2 istore_1
3 iinc 1 by 1
6 getstatic #2 <java/lang/System.out>
9 iload_1
10 invokevirtual #5 <java/io/PrintStream.println>
13 return
- 6 getstatic #2 <java/lang/System.out>:System.out获取静态变量System.out
- 9 iload_1:把局部变量表的索引为1的【10】放到操作数栈中
- 10 invokevirtual #5 <java/io/PrintStream.println>:调用println方法
因此输出结果就是10.
二、代码2:
用与代码1同样的方式分析,我这里就不像代码1那么具体了,方法是一样的。
public void method8(){
int i = 10;
i = i++;
}
代码2的字节码
0 bipush 10
2 istore_1
3 iinc 1 by 1
6 return
你会发现,它与代码1的字节码是一样的!这里仅仅是因为我们只是简单的i++和++i,而没有进行其它的操作,所以,本质山i++和++i如果不做其他的赋值操作的话,这俩其实是效果等价的。
这也就解释了 for(int i =0;i<10;i++) 和 for(int i =0;i<10;++i)其实是一样的
所以这个i如果打印一下,其实也是在索引为1的取索引为1的位置上的【10】,这里我们就不再演示了,感兴趣的同学,可以验证一下。
三、代码 3:
public void method7(){
int i = 10;
int a = i++;
int j = 20;
int b = ++j;
}
字节码指令
0 bipush 10
2 istore_1
3 iload_1
4 iinc 1 by 1
7 istore_2
8 bipush 20
10 istore_3
11 iinc 3 by 1
14 iload_3
15 istore 4
17 return
public void method7();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=5, args_size=1
0: bipush 10
2: istore_1
3: iload_1
4: iinc 1, 1
7: istore_2
8: bipush 20
10: istore_3
11: iinc 3, 1
14: iload_3
15: istore 4
17: return
LineNumberTable:
line 55: 0
line 56: 3
line 58: 8
line 59: 11
line 60: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lcom/atguigu/java/ArithmeticTest;
3 15 1 i I
8 10 2 a I
11 7 3 j I
17 1 4 b I
有了代码1的基础我们可知上述代码,局部变量表容量是5,操作数栈容量是1.
下面我们逐行分析字节码指令:
-
0 bipush 10
-
2 istore_1:
-
3 iload_1:
-
4 iinc 1 by 1
-
7 istore_2
-
8 bipush 20
-
10 istore_3
-
11 iinc 3 by 1
-
14 iload_3
-
15 istore 4
-
17 return 方法结束
最终我们局部变量表是这样子的:
在这里插入图片描述
把结果输出一下看看啥样子
public void method7(){
int i = 10;
int a = i++;
int j = 20;
int b = ++j;
System.out.println(a);
System.out.println(i);
System.out.println(b);
System.out.println(j);
}
结果
10
11
21
21
字节码:
0 bipush 10
2 istore_1
3 iload_1
4 iinc 1 by 1
7 istore_2
8 bipush 20
10 istore_3
11 iinc 3 by 1
14 iload_3
15 istore 4 //0到15 与上面一样,下面才是表示输出的字节码
17 getstatic #2 <java/lang/System.out>
20 iload_2
21 invokevirtual #5 <java/io/PrintStream.println>
24 getstatic #2 <java/lang/System.out>
27 iload_1
28 invokevirtual #5 <java/io/PrintStream.println>
31 getstatic #2 <java/lang/System.out>
34 iload 4
36 invokevirtual #5 <java/io/PrintStream.println>
39 getstatic #2 <java/lang/System.out>
42 iload_3
43 invokevirtual #5 <java/io/PrintStream.println>
46 return
有字节码分析可以知道
- System.out.println(a)的字节码:
17 getstatic #2 <java/lang/System.out>
20 iload_2
21 invokevirtual #5 <java/io/PrintStream.println>
往操作数栈加载是上图索引为2的【10】。所以a输出的结果是10
- System.out.println(i);
24 getstatic #2 <java/lang/System.out>
27 iload_1
28 invokevirtual #5 <java/io/PrintStream.println>
往操作数栈加载是上图索引为1的【11】。所以i输出的结果是11
- System.out.println(b);
31 getstatic #2 <java/lang/System.out>
34 iload 4
36 invokevirtual #5 <java/io/PrintStream.println>
往操作数栈加载是上图索引为4的【21】。所以i输出的结果是21
- System.out.println(j);
39 getstatic #2 <java/lang/System.out>
42 iload_3
43 invokevirtual #5 <java/io/PrintStream.println>
往操作数栈加载是上图索引为3的【21】。所以i输出的结果是21
所以现在你应该体会到了为什么说i++是先赋值再加加,而++i 是先加加再赋值了吧,一定要注意这个口令不是对所有的场景都适用,比如我们一开始举的例子代码1和代码2。
下面来点有意思的:
代码4:
public void method8(){
int i = 10;
i = i++;
System.out.println(i);
}
这个i打印是多少呢?答案是10
字节码指令:
0 bipush 10
2 istore_1
3 iload_1
4 iinc 1 by 1
7 istore_1
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #5 <java/io/PrintStream.println>
15 return
这个我就不画图针对字节码一行一行的解释了。
这里面istore_1两次,后面操作栈中的10把之前局部变量表的11覆盖了,所以打印就是10了,你可以按照我上面的方法,尝试画一下流程,授人以鱼不如授人以渔。
再改一下上面代码:
代码5:
public void method8(){
int i = 10;
i = ++i;
System.out.println(i);//11
}
0 bipush 10
2 istore_1
3 iinc 1 by 1
6 iload_1
7 istore_1
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #5 <java/io/PrintStream.println>
15 return
这个i打印出来就是11
对比一下上面两个字节码,其实最大的不同在于:
代码4是:
3 iload_1
4 iinc 1 by 1
而代码5是:
3 iinc 1 by 1
6 iload_1
这就造成了代码4 加载进操作数栈的是10,代码5 是11,这就造成了再次istore_1时,前者覆盖是10覆盖11,后者是11覆盖11。