【JAVA】字节码分析++i和i++的区别和i=i++失效
序言
闲来无事,记录学习历程,交个朋友。欢迎讨论碰撞,产生好的解决方案!!!👏👏👏
背景
从字节码分析++i和i++的区别,通过错误写法 i = ++i来展示,学习简单的字节码含义,了解Java语言执行基于栈的设计。
IDEA字节码插件
jclasslib 是一款比较好用的Idea 插件,用来看字节码文件

代码示例
通过两个例子来解释++i和i++的区别。
无效写法
代码
package com.gyg.codeing.demo1;
public class TestCount1 {
public static void main(String[] args) {
int res = 0;
for (int i = 0; i < 10; i++) res = res++;
System.out.println(res);
}
}
字节码


分析
- load 命令:用于将局部变量表的指定位置的相应类型变量加载到栈顶;
- store命令:用于将栈顶的相应类型数据保入局部变量表的指定位置;
- const、push:将相应类型的常量放入栈顶
看局部变量表可知,0是args,1是res,2是i,都是我们定义的变量,val(i) 是我自定义的,含义就是i的值。
| 指令 | 代码解释 | 栈 | 指令解释 |
|---|---|---|---|
| iconst_0 | 初始化常量0 | 0 | 入栈,int型常量0进栈 |
| istore_1 | res=0 | 空 | 出栈,栈顶int数值存入第2局部变量 |
| iconst_0 | 初始化常量0 | 0 | 入栈,int型常量0进栈 |
| istore_2 | i=0 | 空 | 出栈,栈顶int数值存入第3局部变量 |
| iload_2 | i的值入栈 | val(i) | 入栈,第3个int型变量进栈 |
| bipush 10 | byte类型10进栈 | val(i),10 | 入栈, byte型常量进栈 |
| if_icmpge | val(i) > 10 | 空 | 比较栈顶两个int大小,当前者大于后者时跳转 |
| iload_1 | res的值进栈 | val(res) | 入栈,栈顶int数值存入第2局部变量 |
| iinc 1 by 1 | 局部变量表res加1 | val(res) | 对局部变量表中index为1的元素加上1 |
| istore_1 | 栈val(res)写入局部变量表res | 空 | 出栈,栈顶int数值存入第2局部变量 |
| iinc 2 by 1 | 局部变量表i加1 | 空 | 对局部变量表中index为2的元素加上1 |
| goto 4 | 跳转 iload_2,For循环 | 空 | 无条件分支,跳转 |
| getstatic | 入栈,获取类的静态字段,将其值压入栈顶 | ||
| iload_1 | 出栈,栈顶int数值存入第2局部变量 | ||
| invokevirtual | 调用实例方法 | ||
| return | 当前方法无返回 |
结论
从字节码指令分析可以看到有一个异常的逻辑
1、iload_1,res值进栈
2、iinc 1 by 1,局部变量表res值+1
3、istore_1,栈顶res值写回局部变量表
栈顶res的值没有变过,覆盖掉了局部变量表res的值,导致 res = res++ 是不生效的
有效写法
代码
package com.gyg.codeing.demo1;
public class TestCount1 {
public static void main(String[] args) {
int res = 0;
for (int i = 0; i < 10; i++) res = ++res;
System.out.println(res);
}
}
字节码


分析
- load 命令:用于将局部变量表的指定位置的相应类型变量加载到栈顶;
- store命令:用于将栈顶的相应类型数据保入局部变量表的指定位置;
- const、push:将相应类型的常量放入栈顶
0 iconst_0
1 istore_1
2 iconst_0
3 istore_2
4 iload_2
5 bipush 10
7 if_icmpge 21 (+14)
10 iinc 1 by 1
13 iload_1
14 istore_1
15 iinc 2 by 1
18 goto 4 (-14)
21 getstatic #7 <java/lang/System.out : Ljava/io/PrintStream;>
24 iload_1
25 invokevirtual #13 <java/io/PrintStream.println : (I)V>
28 return
看局部变量表可知,0是args,1是res,2是i,都是我们定义的变量,val(i) 是我自定义的,含义就是i的值。
| 指令 | 代码解释 | 栈 | 指令解释 |
|---|---|---|---|
| iconst_0 | 初始化常量0 | 0 | 入栈,int型常量0进栈 |
| istore_1 | res=0 | 空 | 出栈,栈顶int数值存入第2局部变量 |
| iconst_0 | 初始化常量0 | 0 | 入栈,int型常量0进栈 |
| istore_2 | i=0 | 空 | 出栈,栈顶int数值存入第3局部变量 |
| iload_2 | i的值入栈 | val(i) | 入栈,第3个int型变量进栈 |
| bipush 10 | byte类型10进栈 | val(i),10 | 入栈, byte型常量进栈 |
| if_icmpge | val(i) > 10 | 空 | 比较栈顶两个int大小,当前者大于后者时跳转 |
| iinc 1 by 1 | 局部变量表res加1 | 空 | 入栈,栈顶int数值存入第2局部变量 |
| iload_1 | res进栈 | val(res) | 对局部变量表中index为1的元素加上1 |
| istore_1 | 栈val(res)写入局部变量表res | 空 | 出栈,栈顶int数值存入第2局部变量 |
| iinc 2 by 1 | 局部变量表i加1 | 空 | 对局部变量表中index为2的元素加上1 |
| goto 4 | 跳转 iload_2,For循环 | 空 | 无条件分支,跳转 |
| getstatic | 入栈,获取类的静态字段,将其值压入栈顶 | ||
| iload_1 | 出栈,栈顶int数值存入第2局部变量 | ||
| invokevirtual | 调用实例方法 | ||
| return | 当前方法无返回 |
结论
我们可以对比异常逻辑,正常逻辑是
1、iinc 1 by 1,局部变量表res值+1
2、iload_1,res进栈
3、istore_1,res出栈,写回局部变量表
这里步骤是多余的,进栈出栈,没有对res进行任何修改,所以最好还是直接写res++
总结
i++是先执行 iload_1指令再执行iinc指令,++i是相反的。
尾声
如果解决有解决你的问题,点点赞,求个关注👍👍👍
天生我材必有用,千金散尽还复来🤞🤞🤞,加油👏👏👏
更多推荐

所有评论(0)