这几天抽着一些时间,把Java的class文件结构研究了一下,再后来就想起了这个令人厌烦的问题,想从字节码指令的角度看看,java到底是怎么处理这个的

先看一段java代码

  1. package bishi;
  2. public class PlusPlusTest {
  3. public static void main(String[] args) {
  4. int i = 1;
  5. i = i++ + i++ + i++;
  6. System.out.println(i);
  7. int j = 1;
  8. j = ++j + ++j + ++j;
  9. System.out.println(j);
  10. }
  11. }


打印的i和j分别是多少呢?

先分析i++:

  1. int i = 1;
  2. i = i++ + i++ + i++;
  3. System.out.println(i);



这三句对应的字节码为:

  1. 0  iconst_1       //将int类型的数字1push到栈顶
  2. 1  istore_1 [i]   //将栈顶的1存到变量i中, 这就是int i=1;
  1. 2  iload_1 [i]    //把i的值再压栈,即栈顶存入1
  2. 3  iinc 1 1 [i]   //把i变量中的值加1,此时i中存的是2,但是栈顶的值是1
  3. 6  iload_1 [i]    //把i的值压栈,此时栈顶和栈顶第二个分别是2,1
  4. 7  iinc 1 1 [i]   //把i变量中的值加1,此时i中存的是3,
  5. 10  iadd           //把栈顶两个int类型的值相加,并把结果存入栈顶,即现在的栈,从栈顶往下依次存了3,2,1
  6. 11  iload_1 [i]    //把i压栈,栈中自顶向下依次是3,3,2,1
  7. 12  iinc 1 1 [i]   //把i变量中的值再加1,i中存的是4了
  8. 15  iadd           //同10,执行完之后,栈顶为6
  9. 16  istore_1 [i]   //将栈顶int型值存入i中,即执行完此步,i=6,到这一步就是整个 i = i++ + i++ + i++;
  10. 17  getstatic java.lang.System.out : java.io.PrintStream [16]
  11. 20  iload_1 [i]    //这一步把i中的6再次push到栈顶
  12. 21  invokevirtual java.io.PrintStream.println(int) : void [22]   //输出栈顶的6



java虚拟机内存空间中存在一个叫java方法栈的区域,在这里为每个方法提供一个栈帧,在栈帧中存放了该方法的局部变量表,操作栈,动态链接,方法出口等信息。上面字节码中指令所指的栈应该就是这个操作栈吧(个人理解,不保证正确~)。

分析完i++,下面来看看++j的原理

  1. int j = 1;
  2. j = ++j + ++j + ++j;
  3. System.out.println(j);



对应的字节码为:

  1. 24  iconst_1
  2. 25  istore_2 [j]     //int j=1;
  3. 26  iinc 2 1 [j]     //把j加1,j=2
  4. 29  iload_2 [j]      //把j的值压栈,此时栈顶为2
  5. 30  iinc 2 1 [j]     //把j加1,j=3
  6. 33  iload_2 [j]      //把j的值压栈,栈顶往下依次为3,2
  7. 34  iadd             //把栈顶两int类型数相加,结果压栈,栈顶往下:5,3,2
  8. 35  iinc 2 1 [j]     //把j加1,j=4
  9. 38  iload_2 [j]      //把j压栈,栈顶向下:4,5,3,2
  10. 39  iadd             //把栈顶两int相加,结果压栈,栈顶往下:9,4,5,3,2
  11. 40  istore_2 [j]     //把栈顶存入j,j=9,至此,j = ++j + ++j + ++j;执行完毕
  12. 41  getstatic java.lang.System.out : java.io.PrintStream [16]
  13. 44  iload_2 [j]
  14. 45  invokevirtual java.io.PrintStream.println(int) : void [22]



总结

i++和++i的区别就是在进入操作栈和自加的顺序,呃,说完怎么跟没说一样……

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.6.1

  1. public class Test {
  2. public static void main(String[] args) {
  3. int i=0;
  4. i=i++;
  5. System.out.println(i);
  6. }
  7. }
  8. 结果是0
  9. 为什么是0 因为:
  10. 是因为Java编译器的原因:我们来看看编译后的字节码
    1. 0  iconst_0 //将int型0推送至栈顶
      1  istore_1 //将栈顶int型数值存入第二个本地变量 i=0 (int i=0)
      2  iload_1 //将第二个int型本地变量推送至栈顶  然后将i推送至栈顶 0
      3  iinc 1 1 //将指定int型变量增加指定值(i++, i--, i+=2)完成i++
      4  istore_1 //将栈顶int型数值存入第二个本地变量 将栈元素赋值给了i i=0
      5  getstatic java/lang/System/out Ljava/io/PrintStream;
      6  iload_1

      1. public static void main(String[] args) {
      2. int i=0;
      3. i++;
      4. System.out.println(i);
      5. }
      6. 字节码为:
      7. iconst_0 //将int型0推送至栈顶
      8. istore_1 //将栈顶int型数值存入第二个本地变量 i=0 (int i=0)
      9. iinc 1 1 //将指定int型变量增加指定值(i++, i--, i+=2)完成i++
        1. public static void main(String[] args) {
        2. int i=0;
        3. int k=i++;
        4. System.out.println(i);
        5. }
        6. 字节码为:
        7. 0  iconst_0 //将int型0推送至栈顶
          1  istore_1 //将栈顶int型数值存入第二个本地变量 i=0 (int i=0)
          2  iload_1 //将第二个int型本地变量推送至栈顶  然后将i推送至栈顶 0
          3  iinc 1 1 //将指定int型变量增加指定值(i++, i--, i+=2)完成i++
          4  istore_2 //将栈顶int型数值存入第三个本地变量 将栈元素赋值给了i i=0
          5  getstatic java/lang/System/out Ljava/io/PrintStream;
          6  iload_1
          1. public static void main(String[] args) {
          2. int i=0;
          3. int k=++i;
          4. System.out.println(i);
          5. }
          6. 字节码为:
          7. 0 iconst_0
            1 istore_1
            2 iinc 1 by 1
            5 iload_1
            6 istore_2

    2. 对比而言,对于i++而言,i=i++指令多了两步,2和4
      其实这两步是赋值符号引起的,有意思的是第二步出现的时机,是在iinc之前,这就是因为java lang spec中规定的。
    3. java编译器对于++i 并不会生成2和4
    4. int i=0 i=i++ 或者 int i=0 int j= i++ 相当于 int temp = i i++ i=temp int temp = i i++ j=temp
    5. int i=0 i=++i 相当于 ++i temp = i i=temp;
    6. 总结:java编译器对于i++会先将i的值保存至另一变量 然后在对i++,另一变量仍没有改变。 而对于++i 是先对i++ 然后保存到另一变量 然后赋值。

http://www.cnblogs.com/eggbucket/archive/2012/05/21/2511926.html

先看4个题目:

①int i = 0;

i = i++;

②int i = 0;

i = ++i;

③int i = 0;

int j = 0;

j = i++ + i++;

④ int i = 0;

int j = 0;

j = i++ + i++ + i++;

每道题里的i和j都是多少?

结果分别是
①i = 0,
//出现0可以这样简单理解:自增是单独的一个操作,自增的结果不会体现在变量i上。i++,本身就是逻辑运算(i=i+1)的缩写。
//容易混淆的地方就在于定义的变量i自增操作的变量i名称相同,如果写成这样i=i;temp=temp+1;的两行代码,就没有疑惑了
②i = 1,
③i = 2,j = 1,
④i = 3,j = 3。

i++和++i的问题,困扰很多人。现在通过分析字节码,来确定这两条语句究竟是怎样执行的。

先给出今天要用到的字节码的含义

Bytecode

Stack

before->after

Description

iconst_0

->0

Loads the int value 0 onto the stack

istore_1

value->

Store int value into variable 1

istore_2

value->

Store int value into variable 2

iinc

No change

Increment local variable #index by signed byte const

iload_1

->value

Loads an int value from variable 1

iadd

value 1,value 2->result

Adds 2 ints together

说明两点需要注意的地方:

①iinc操作是有参数的,但是在此忽略,简写为iinc,此操作对应于自加操作,并且该操作不对stack有任何改变;

②iadd操作过后只在stack中保留结果result。

接下来是四段程序主要的字节码:

①iconst_0        ②iconst_0        ③iconst_0       ④iconst_0

istore_1            istore_1            istore_1           istore_1

iload_1             iinc 1,1              iconst_0          iconst_0

iinc 1,1             iload_1              istore_2           istore_2

istore_1            istore_1            iload_1             iload_1

iinc 1,1             iinc 1,1

iload_1             iload_1

iinc 1,1             iinc 1,1

iadd                  iadd

istore_2            iload_1

iinc 1,1

iadd

istore_2

现在解释①。第一步在stack中存入一个int常量0;第二步把它赋值给第一个变量,即我们的i;第三步把第一个变量i的值存入到stack中;第四步在i自身的空间进行自加,而第三步存入到stack中的值没有变;第五步把第三步存入到stack中的值再赋值给第一个变量i。也就是说,i真的是进行自加了,但是被自己原来的值覆盖掉了。从这里我们可以看出,自加操作比赋值操作的优先级高。

再看②。和①比较起来,区别就在于,++i是先进行自加,然后把自加后的值存入到stack中,所以最后赋值给i的是1,不是0。

③呢?首先,③比前两个多了一个变量,但这不是重点。往下看,把第一个变量i的值(0)存入到stack中,i自加,再第一个变量i的值(1)存入到stack中,再自加。此时stack中有两个值了,0和1,进行加操作后的结果是1,然后赋值给第二个变量j。这里颠覆了我们以前被告知的,加的运算优先级比自加高。这就是困扰我们的,其实,只要把值读入到stack中,接下来就进行自加,只有得到了两个参数,才进行加操作。

来看④。前面都和③一样,只到进行了第一次加操作,得到的结果(1)保存在stack中,没有赋值给j,然后再次读入i的值(2)到stack,i自加,得到加操作的两个参数,1和2,进行第二次加操作的结果是3,赋值给j。

到此,问题都解决了。

http://blog.csdn.net/tutuhatec/article/details/6747774

用java字节码解释i++和++i(转)的更多相关文章

  1. 【JVM源码解析】模板解释器解释执行Java字节码指令(上)

    本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...

  2. Java字节码操纵框架ASM小试

    本文主要内容: ASM是什么 JVM指令 Java字节码文件 ASM编程模型 ASM示例 参考资料汇总 JVM详细指令 ASM是什么 ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既 ...

  3. jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

    动态类型语言 动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译期. 举例子解释“类型检查”,例如代码: obj.println("hello world"); 假 ...

  4. Java字节码里的invoke操作&&编译时的静态绑定与动态绑定

    一个一直运行正常的应用突然无法运行了.在类库被更新之后,返回下面的错误. Exception in thread "main" java.lang.NoSuchMethodErro ...

  5. JVM 内部原理(六)— Java 字节码基础之一

    JVM 内部原理(六)- Java 字节码基础之一 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...

  6. Java字节码分析

    目录 Java字节码分析 查看字节码详细内容 javap 实例分析 Java字节码分析 对于源码的效率,但从源码来看有时无法分析出准确的结果,因为不同的编译器版本可能会将相同的源码编译成不同的字节码, ...

  7. 从Java源码到Java字节码

    Java最主流的源码编译器,javac,基本上不对代码做优化,只会做少量由Java语言规范要求或推荐的优化:也不做任何混淆,包括名字混淆或控制流混淆这些都不做.这使得javac生成的代码能很好的维持与 ...

  8. 3种骚操作,教你查看 Java 字节码!

    在我们工作.学习.以及研究 JVM 过程当中,不可避免的要查看 Java 字节码,通过查看字节码可以了解一个类的编译结果,也能通过编译器层面来分析一个类的性能. 字节码文件是不能直接打开的,下面栈长教 ...

  9. Java字节码扩展

    异常表 代码一: public class Test03 { public void test() { try { InputStream is = new FileInputStream(" ...

随机推荐

  1. ios pop 折叠动画

    今天写了一个很有趣的电影太,我们可以去githoub下载. 这部动画是高级写作,我参考了它.而凝视,我希望你能看的懂. 各种动画.事实上,一些不起眼的开始.我也只是摸索. 我希望有更多的交流.[   ...

  2. filestream.read(buffer,offset,count)的正确解释

    filestream.read(buffer,offset,count) offset是buffer的偏移量 所以,filestream.read(buffer,1,count)会报下面的错 Syst ...

  3. WPF技术触屏上的应用系列(五): 图片列表异步加载、手指进行缩小、放大、拖动 、惯性滑入滑出等效果

    原文:WPF技术触屏上的应用系列(五): 图片列表异步加载.手指进行缩小.放大.拖动 .惯性滑入滑出等效果 去年某客户单位要做个大屏触屏应用,要对档案资源进行展示之用.客户端是Window7操作系统, ...

  4. 构造NFS

    一.设备nfs-utils 伺服器: [root@server05 ftp]# yum install nfs-utils 这时会自己主动安装rpcbind需将此服务重新启动nfs服务才干启动 cli ...

  5. 编程算法 - 分割数 代码(C)

    分割数 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 有n个无差别的物品, 将它们划分成不超过m组, 求出划分方法数模M的余数. 比如: n= ...

  6. NEON简单介绍

    个128位四字寄存器Q0-Q15,32个64位双字寄存器D0-D31,两个寄存器是重叠的,在使用的时候须要特别注意,不小心就会被覆盖掉. NEON的数据类型:无符号整数.有符号整数.未指定类型的整数. ...

  7. 【Android进阶】使用第三方平台ShareSDK实现新浪微博的一键分享功能

    在公司最近的一个项目中,需要实现一键分享功能,在这里我使用的是第三方平台ShareSDK,将使用经验与大家分享 先看效果图 主界面 分享界面 由于第一次使用,所以需要先进行新浪授权,授权界面 分享结果 ...

  8. 旧Mj下拉刷新 An instance 0xca90200 of class UITableView was deallocated while key value observers were s

    An instance 0xca90200 of class UITableView was deallocated while key value observers were still regi ...

  9. SDUT 2894-C(最短spfa)

    C Time Limit: 7000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描写叙述 给出一个带权无向图.包括n个点,m条边.求出s,e的最短路.保证最短路存在 ...

  10. 第三章_JSP

    3.1.JSP概述 Jsp页面实在jsp容器中执行的.Servlet容器一般也是JSP容器.比如,Tomcat就是一个Servlet/JSP容器. 第一次请求一个jsp页面时,Servlet/JSP容 ...