i++跟++i在JVM字节码上的区别

大家都知道i++跟++i的区别:

  1. i++是先赋值再运算
  2. ++i是先运算再赋值

那可能很多人没有写过i=i++或者i=++i,这样的骚语句,这个时候是什么样的情况呢?
可能很多人能想到i=i++,结果i=0,因为先赋值,这时候值是0,所以i=0。
i=++i最终结果i=1,因为是先运算+1再赋值,这时候已经是1了。
那到底是做了什么样的操作来实现这样的结果的呢?我们来追根溯源,正常情况,我们应该先看一下class文件显示什么东西,那我们写了几个最简单的方法如下:

public class TestI {
    public void testMethod() {
        int i = 0;
        i = i + 2;
    }
    public void testMethodA() {
        int i = 0;
        i = i++;
    }
    public void testMethodB() {
        int i = 0;
        i = ++i;
    }
}

这里为了明白正常的i+1在java中是怎么处理的,增加了一个i=i+2的对照组。
我们用javac TestI.java编译成class文件,如下:

    public void testMethod() {
        byte var1 = 0;
        int var2 = var1 + 2;
    }

    public void testMethodA() {
        byte var1 = 0;
        int var2 = var1 + 1;
    }

    public void testMethodB() {
        byte var1 = 0;
        int var2 = var1 + 1;
    }

testMethodA竟然跟testMethodB是一样的。Emmm,因吹斯听,应该是IDE在翻译字节码的时候没有看出来这俩的区别?那我们还是来看字节码好了。
javap -c TestI.class命令查看字节码如下:

这里需要的知识储备是JVM指令集JVM 栈帧之操作数栈与局部变量表,默认读者了解不在赘述。

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

  public void testMethod();
    Code:
       0: iconst_0                          //将int型0压栈至栈顶
       1: istore_1                          //将栈顶int型数值存入第1个本地变量
       2: iload_1                           //将第1个int型本地变量压栈至栈顶
       3: iconst_2                          //将int型2压栈至栈顶
       4: iadd                              //栈顶两个数相加并进栈
       5: istore_1                          //将栈顶int型数值存入第1个本地变量
       6: return                            //从当前方法返回void

  public void testMethodA();
    Code:
       0: iconst_0                          //将int型0压栈至栈顶
       1: istore_1                          //将栈顶int型数值存入第1个本地变量
       2: iload_1                           //将第1个int型本地变量压栈至栈顶
       3: iinc          1, 1                //将指定int型变量增加指定值,可以有两个变量,分别表示index, const,index指第index个int型本地变量,const增加的值,所以是第1个本地变量增加1
       6: istore_1                          //将栈顶int型数值存入第1个本地变量
       7: return                            //从当前方法返回void

  public void testMethodB();
    Code:
       0: iconst_0                          //将int型0压栈至栈顶
       1: istore_1                          //将栈顶int型数值存入第1个本地变量
       2: iinc          1, 1                //将指定int型变量增加指定值,第1个本地变量增加1
       5: iload_1                           //将第1个int型本地变量压栈至栈顶
       6: istore_1                          //将栈顶int型数值存入第1个本地变量
       7: return                            //从当前方法返回void
}

我们做了一个最简单的i=i+2的对照组来看正常情况下这个最简单的增量操作是长什么样子的,它的具体流程是先把i在本地变量表里面初始化出来,再把i的值放到操作数栈,再给操作数栈放一个需要加的2,然后i跟2相加,得到的结果再存到本地变量完成相加操作。
而i = i++或者i = ++i的操作还是跟i=i+1有很大区别的。最大的区别就是i=i+2是用了iadd,而i++是用了iinc,iadd是作用在操作数栈中的,而iinc是直接在本地变量表中直接把变量增加。
来看i++++i,其实就是iinciload的运行顺序的区别,印证了我们之前所说的这两者的区别,我们再赘述一遍两者的区别,看是怎么体现出来的:

  1. i++是先赋值再运算
  2. ++i是先运算再赋值

i++是先iload_1把0这个值推到操作数帧顶部,再iinc把本地变量表里面的i做+1操作,这个操作结束后意味着这时候在操作数栈里面代表i的值仍然是+1之前的值也就是0,而其实本地变量表中的值已经是1,但是得下一个再iload1的时候才是1代表i出战。
而++i,正好相反是先iinc做+1操作,然后再代表i出证,这时候的值已经变成了1。
所以后面执行到i=i++还是i=++i的共同代码i=,在字节码中也就是istore_1,把这时候栈中代表i出征的值赋值回本地变量表中的i,所以这时候i=i++操作数栈里面的0覆盖了本地变量表中的1。

所以i=i++比i++做得多此一举事情就是多做了一个i=, 把操作数栈中的0覆盖了本地变量表中的正确值1.

说一千道一万,什么都比不上一张图来的直观:

i++2.png

那既然i++是先赋值再运算,那我们多做几次i=i++是不是就好了,比如加个while循环,like below:

    public void testMethodC() {
        int i = 0;
        for(int j = 0;j<100;j++){
            i = i++;
        }
    }

  public void testMethodC();
    Code:
       0: iconst_0                          //将int型0压栈至栈顶
       1: istore_1                          //将栈顶int型数值存入第1个本地变量
       2: iconst_0                          //将int型0压栈至栈顶
       3: istore_2                          //将栈顶int型数值存入第2个本地变量
       4: iload_2                           //将第2个int型本地变量推送至栈顶
       5: bipush        100                 //将将单字节的常量值100推送至栈顶
       7: if_icmpge     21                  //栈顶弹出两个值,比较两int型数值大小,当结果大于等于0时跳转到21
      10: iload_1                           //将第1个int型本地变量推送至栈顶
      11: iinc          1, 1                //将指定int型变量增加指定值,第1个本地变量增加1
      14: istore_1                          //将栈顶int型数值存入第1个本地变量
      15: iinc          2, 1                //将指定int型变量增加指定值,第2个本地变量增加1
      18: goto          4                   //跳转到偏移位4
      21: return

其实想一想还是0哈,因为每次的i都是重新load到操作数栈的,之前的+1过来的又会被覆盖,不管循环多少次都是一样的:


while3.png

总结:

1. i=i+1用的是iadd指令,作用是弹出操作数栈顶部的两个值相加并把结果再压入栈顶。
2. i++用的是iinc指令,直接操作的是本地变量表的数值。
3. i=i++结果是0是因为本地变量表中i加一了但是操作数栈中的仍然是0,最后赋值的时候0把1覆盖掉了。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容