深入剖析i++和++i的区别(字节码层面)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


一、代码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。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值