JVM笔记:i++ 和++i的字节码原理详解

2,257 阅读4分钟

前言

一个关于i++和++i的原理详解,对字节码不太熟悉的可以先了解下字节码相关知识,因为从字节码角度更能理解两者的原理。

案例

  int i = 0, j = 0;
        i = i++;
        j =++ j;
        System.out.println("args = [" + i + "]");
        System.out.println("args = [" + j + "]");
输出结果:
args = [0]
args = [1]

对于i++,++i可能大家的第一反应的理解是:前者先运算再自增赋值,后者先自增赋值在运算。那么上面那个例子为什么会得出这个结果,下面查看其字节码,输出的我就忽略不展示了。

         0: iconst_0
         1: istore_1
         2: iconst_0
         3: istore_2
         4: iload_1
         5: iinc          1, 1
         8: istore_1
         9: iinc          2, 1
        12: iload_2
        13: istore_2

0 ~ 3行很容易理解,在局部变量保存i和j 分别设值为0。注意看4 ~ 8和9 ~ 13,他们分别代表的是i=i++j=++j。他们之间的差别就在于load指令一个在自增指令`iinc``前,一个在后

这里说一下iinc指令,它是int类型的局部变量自增指令(将后者数据加到前者下标的int类型局部变量中)。可以看到这个指令和下一条指令之间左边的行号是少了两行的,那是因为这个指令显示的是一条,但是干了三件事,读取该值,自增加一,将自增后的值保存回局部变量。

下面开始分析,执行i=i++操作时(4 ~ 8),首先iload_1使局部变量i(0)入栈,然后执行iinc注意,执行完iinc后局部变量表中的i是已经变成1了,iinc并不是吃干饭的,但是前一条指令入栈的值没变,还是0,然后紧接着对i做等号赋值操作的istore_1指令将栈顶的0又保存回了局部变量1(i)中,所以局部变量表中的值变化是:0 ~ 1 ~ 0;

不同的是,执行j=j++操作时(9 ~ 13),先自增后局部变量表中j的值已经变成了1,然后执行=号赋值操作,将++j这一整个iinc后的结果也就是局部变量表2中的值入栈(iload_2),然后赋值给j(istore_2)。

从上面的理解中可以看到俗称的:先运算在赋值和先赋值在运算,其原理就在于iinc指令是否先于局部变量的入栈赋值操作,下面再给几个拓展例子看看。

例一:
       int i = 0, j = 0;
        i=i++ + i++;
        j= ++j + j++; 
例二:
       int i = 0, j = 0;
        j=i++ + i++;

例三:
        int i = 0, j = 0;
        j=i++ + ++i;

例四:
       int i = 0, j = 0;
        j=i++ + ++j;

上面我不列答案,大家可以自己先猜测是多少,然后在看看自己分析的对不对。

虚假的分割线-------------------------------------------------------------------

例一:第一个自增和前面的步骤一样,入栈i然后自增iinc,但是第二个自增会重新把i从局部变量表入栈,此时i经过前面自增已经为1了,然后第二个自增后再赋值,结果就是i=0+1且此时局部变量表中的i值为2(这是等于号赋值前的值,赋值后又重新保存变为1了)。 j的运算我就简要说一下了,和前面类似:先iinc自增运算(差异),然后j(1)入栈,然后出栈保存,再入栈j(1)入栈,自增运算,然后相加。结果是j=1+1,且此时局部变量表中的j为2,下面是字节码:

        4: iload_1
         5: iinc          1, 1
         8: iload_1
         9: iinc          1, 1
        12: iadd
        13: istore_1
        14: iinc          2, 1
        17: iload_2
        18: iload_2
        19: iinc          2, 1
        22: iadd
        23: istore_2

例二:例二和例一不同的是最后的赋值对象从自身变成了其他人,所以这里和例一i运算解析是一模一样的,只不过是最后括号内那句话中的赋值对象变成了j,所以此时i=2,j=1

例三:例三和例一j运算解析也是类似的,同样改变的是最后的赋值对象`,所以此时i=2,j=2

例四:例四虽然有点小不同,但是其原理还是一样的,先入栈i再自增运算i++,此时栈中的i为,0,变量表中的i为1;然后自增运算++j,在入栈j,相加得出结果再保存回变量表,所以结果就是j=0+1,这里得出的j=1是相加运算得出的j=1,并不是自增指令得出的j=1,比如此时i为4的话,结果就是i=5,j=5

      4: iload_1
      5: iinc          1, 1
      8: iinc          2, 1
      11: iload_2
      12: iadd
      13: istore_2

总结

其实以前也看过i++和++i区别的文章,但是看过不就又忘了,最近在学习JVM相关知识,结果字节码对这一原理有了更深的了解,起码在脑子里这个知识点的保质期会久一点。