序言

闲来无事,记录学习历程,交个朋友。欢迎讨论碰撞,产生好的解决方案!!!👏👏👏

背景

从字节码分析++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初始化常量00入栈,int型常量0进栈
istore_1res=0空出栈,栈顶int数值存入第2局部变量
iconst_0初始化常量00入栈,int型常量0进栈
istore_2i=0空出栈,栈顶int数值存入第3局部变量
iload_2i的值入栈val(i)入栈,第3个int型变量进栈
bipush 10byte类型10进栈val(i),10入栈, byte型常量进栈
if_icmpgeval(i) > 10空比较栈顶两个int大小,当前者大于后者时跳转
iload_1res的值进栈val(res)入栈,栈顶int数值存入第2局部变量
iinc 1 by 1局部变量表res加1val(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初始化常量00入栈,int型常量0进栈
istore_1res=0空出栈,栈顶int数值存入第2局部变量
iconst_0初始化常量00入栈,int型常量0进栈
istore_2i=0空出栈,栈顶int数值存入第3局部变量
iload_2i的值入栈val(i)入栈,第3个int型变量进栈
bipush 10byte类型10进栈val(i),10入栈, byte型常量进栈
if_icmpgeval(i) > 10空比较栈顶两个int大小,当前者大于后者时跳转
iinc 1 by 1局部变量表res加1空入栈,栈顶int数值存入第2局部变量
iload_1res进栈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是相反的。

尾声

如果解决有解决你的问题,点点赞,求个关注👍👍👍
天生我材必有用,千金散尽还复来🤞🤞🤞,加油👏👏👏

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐