关于i++与++i的一道题

前几天刷LeetCode题,看到评论区有人问了一个有意思的问题,代码如下:

public static void main(String[] args) {
	int a[] = {1, 2, 3, 4, 5};
	int num = 3;
	a[num - 1] = a[num-- - 1];
	for (int i : a) {
		System.out.println(i);
	}
}

问输出结果应该是1、2、3、4、5,还是1、2、2、4、5?

那如果把num--变成--num,结果又是什么?

一般上学认真听讲的可能都知道结果,num--的时候输出1、2、3、4、5。

--num的时候,输出1、2、2、4、5。

从上学刚学编程,老师就教过,i--是先取i的值,然后i再减1。--i是先减1,再取i的值。

本着刨根问底的精神,特别想知道这两个操作虚拟机到底是怎么执行的。我们javap -c看一下字节码是什么样的。

先看--num时候:

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5                  
       1: newarray       int        
       3: dup                     
       4: iconst_0                 
       5: iconst_1                  
       6: iastore                
       7: dup                       
       8: iconst_1
       9: iconst_2
      10: iastore
      11: dup
      12: iconst_2
      13: iconst_3
      14: iastore
      15: dup
      16: iconst_3
      17: iconst_4
      18: iastore
      19: dup
      20: iconst_4
      21: iconst_5
      22: iastore
      23: astore_1
      24: iconst_3
      25: istore_2
      26: aload_1
      27: iload_2
      28: iconst_1
      29: isub
      30: aload_1
      31: iinc          2, -1
      34: iload_2
      35: iconst_1
      36: isub
      37: iaload
      38: iastore
      39: aload_1
      40: astore_3
      41: aload_3
      42: arraylength
      43: istore        4
      45: iconst_0
      46: istore        5
      48: iload         5
      50: iload         4
      52: if_icmpge     75
      55: aload_3
      56: iload         5
      58: iaload
      59: istore        6
      61: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      64: iload         6
      66: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      69: iinc          5, 1
      72: goto          48
      75: return
}

我们从头开始逐行分析:

0:iconst_5:表示常量5入栈

1:newarray int:分配int类型数组,需要弹出栈顶数据作为数组长度

3:dup:复制栈顶内容,这里就是复制了一份数组

接下来4到22行,都是数据赋值,我们只看一组4到7行:

4:iconst_0:常量0入栈

5:iconst_1:常量1入栈

6:iastore:将栈顶int数据存入指定数组的指定索引位置,需要出栈3次,所以每次赋值后都要dup复制栈顶元素。

数据初始化完,从23行继续看:

23:astore_1:将栈顶元素存入第二个本地变量,在java方法中局部变量数组中按顺序要放入:this(static方法没有)、参数、局部变量。所以这里第二个变量就是我们声明的数组a。

此时栈分布是空的:

 
 

 

接下来24、25行:

24:iconst_3:常量3入栈

3
 

 

25:istore_2:将栈顶元素保存到第三个本地变量,就是给我们声明的num赋值3

 
 

 

 

接下来就是取值了:

26:aload_1:将第二个引用类型入栈,这里就是数组a入栈

27:iload_2:将第三个整数类型入栈,这里就是num入栈

28:iconst_1:常量1入栈

1
num=3
a
 

 

 

29:isub:栈顶两个元素减法,结果入栈。就是num-1结果入栈

2
a

 

30:aload_1:将第二个引用类型入栈,这里就是数组a入栈

a

2

a

 

31:iinc 2,-1:将第三个本地变量与-1相加,此时num变为2

34:iload_2:将第三个整数类型入栈,这里就是num入栈

35:iconst_1:常量1入栈

1
num=2
a
2
a

 

36:isub:栈顶两个元素减法,结果入栈,即num-1

1
a
2
a

 

37:iaload:int类型数组栈顶位置索引的数值入栈,这里就是a[num-1]入栈

a[1]=2
2
a

 

38:iastore:将栈顶int类型数据存储到int类型数组指定索引位置。

这里也就是a[2]=a[1],所以打印结果是1、2、2、4、5。

 

我们再看下num--情况下的字节码:

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5
       1: newarray       int
       3: dup
       4: iconst_0
       5: iconst_1
       6: iastore
       7: dup
       8: iconst_1
       9: iconst_2
      10: iastore
      11: dup
      12: iconst_2
      13: iconst_3
      14: iastore
      15: dup
      16: iconst_3
      17: iconst_4
      18: iastore
      19: dup
      20: iconst_4
      21: iconst_5
      22: iastore
      23: astore_1
      24: iconst_3
      25: istore_2
      26: aload_1
      27: iload_2
      28: iconst_1
      29: isub
      30: aload_1
      31: iload_2
      32: iinc          2, -1
      35: iconst_1
      36: isub
      37: iaload
      38: iastore
      39: aload_1
      40: astore_3
      41: aload_3
      42: arraylength
      43: istore        4
      45: iconst_0
      46: istore        5
      48: iload         5
      50: iload         4
      52: if_icmpge     75
      55: aload_3
      56: iload         5
      58: iaload
      59: istore        6
      61: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      64: iload         6
      66: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      69: iinc          5, 1
      72: goto          48
      75: return
}

唯一的区别就在于31行,在num--的情况下,执行iinc 2,-1前,先执行了iload_2,即对局部变量修改前,提前把局部变量值入栈。

至此,分析完毕。

通过分析字节码追溯该问题的根源,还是很有意思的。

字节码其实在编码中还是会常用到的,比如虚拟机是怎么做try...catch的等等,从字节码上可以很清楚的理解。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值