i=i++问题

转载:
http://www.blogjava.net/dreamstone/archive/2006/11/04/79058.html
http://blog.csdn.net/hblis1991/article/details/48317559

  1. 下面是一到Java笔试题:

复制代码
1 public class Test2
2 {
3 public void add(Byte b)
4 {
5 b = b++;
6 }
7 public void test()
8 {
9 Byte a = 127;
10 Byte b = 127;
11 add(++a);
12 System.out.print(a + ” “);
13 add(b);
14 System.out.print(b + “”);
15 }
16 }
复制代码
2. 为方便分析起见,将打印的语句去掉,如下:

复制代码
1 public void add(Byte b)
2 {
3 b = b++;
4 }
5 public void test()
6 {
7 Byte a = 127;
8 Byte b = 127;
9 add(++a);
10 add(b);
11 }
复制代码
3. 将上述代码反编译,得到如下字节码:

  public void add(java.lang.Byte);
      Code:
         0: aload_1
         1: astore_2
         2: aload_1
         3: invokevirtual #2                  // Method java/lang/Byte.byteValue:(
  )B
        6: iconst_1
        7: iadd
        8: i2b
        9: invokestatic  #3                  // Method java/lang/Byte.valueOf:(B)
 Ljava/lang/Byte;
       12: dup
       13: astore_1
       14: astore_3
       15: aload_2
       16: astore_1
       17: return

 public void test();
     Code:
        0: bipush        127
        2: invokestatic  #3                  // Method java/lang/Byte.valueOf:(B)
 Ljava/lang/Byte;
        5: astore_1
        6: bipush        127
       8: invokestatic  #3                  // Method java/lang/Byte.valueOf:(B)
 Ljava/lang/Byte;
      11: astore_2
      12: aload_0
       13: aload_1
       14: invokevirtual #2                  // Method java/lang/Byte.byteValue:(
 )B
       17: iconst_1
       18: iadd
       19: i2b
       20: invokestatic  #3                  // Method java/lang/Byte.valueOf:(B)
 Ljava/lang/Byte;
       23: dup
       24: astore_1
       25: invokevirtual #4                  // Method add:(Ljava/lang/Byte;)V
       28: aload_0
       29: aload_2
       30: invokevirtual #4                  // Method add:(Ljava/lang/Byte;)V
       33: return
 }
  1. 字节码很长,看着发怵,不用怕,我们将字节码分成两部分:add方法和test方法。

  2. 我们先来看add方法:

  add方法局部变量表
  下标:  0         1                2                    3
  标记: this   形参Byte b   Byte型临时变量tmp     Byte型临时变量tmp2
  值  :          -128             -128                  -127
  public void add(java.lang.Byte);
      Code:
         0: aload_1          // 局部变量表中下标为1的引用型局部变量b进栈      
         1: astore_2         // 将栈顶数值赋值给局部变量表中下标为2的引用型局部变量tmp,栈顶数值出栈。
         2: aload_1           // 局部变量表中下标为1的引用型局部变量b进栈 
        3: invokevirtual #2 // 自动拆箱,访问栈顶元素b,调用实例方法b.byteValue获取b所指Byte
                            // 对象的value值-128,并压栈
        6: iconst_1           // int型常量值1进栈
        7: iadd               // 依次弹出栈顶两int型数值1(0000 0001)、-128(1000 0000)
                            //(byte类型自动转型为int类型)相加,并将结果-127(1000 0001)进栈
        8: i2b               // 栈顶int值-127(1000 0001)出栈,强转成byte值-127(1000 0001),并且结果进栈
        9: invokestatic  #3 // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
                            // 返回value值为-127的Byte对象的地址,并压栈
       12: dup               // 复制栈顶数值,并且复制值进栈
      13: astore_1           // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量b,栈顶数值出栈。此时b为-127
      14: astore_3           // 将栈顶数值赋值给局部变量表中下标为3的引用型局部变量tmp2,栈顶数值出栈。此时tmp2为-127
       15: aload_2           // 局部变量表中下标为2的引用型局部变量tmp进栈,即-128入栈  
       16: astore_1         // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量b,栈顶数值出栈。此时b为-128
       17: return

总结一下上述过程,核心步骤为b = b++;分为三步:参考:http://blog.csdn.net/brooksychen/article/details/1624753

①把变量b的值取出来,放在一个临时变量里(我们先记作tmp);

②把变量b的值进行自加操作;

③把临时变量tmp的值作为自增运算前b的值使用,在本题中就是给变量b赋值。

到此可得出结论,add方法只是个摆设,没有任何作用,不修改实参的值。

  1. 搞懂了add方法,我们接下来分析test方法:

这里需要说明两点:

(1)由于Byte类缓存了[-128,127]之间的Byte对象,故当传入的实参byte相同时,通过Byte.valueOf(byte)返回的对象是同一个对象,详见Byte源码。

(2)如果是实例方法(非static),那么局部变量表的第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中通过this访问。详见:http://wangwengcn.iteye.com/blog/1622195

 test方法局部变量表
  下标:  0         1                2                    
  标记: this   形参Byte a   Byte型临时变量b 
  值  :          -128             127        
  public void test();
     Code:
         0: bipush        127        // 将一个byte型常量值推送至操作数栈栈顶
         2: invokestatic  #3        // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
                                  // 返回value值为127的Byte对象的地址,并压栈
       5: astore_1                // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量a,栈顶数值出栈。此时a为127
        6: bipush        127        // 将一个byte型常量值推送至操作数栈栈顶
        8: invokestatic  #3        // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
                                 // 返回value值为127的Byte对象的地址,并压栈。这里需要说明一点,
                                 // 由于Byte类缓存了[-128,127]之间的Byte对象,故当传入的实参byte相同时,
                                 // 通过Byte.valueOf(byte)返回的对象是同一个对象,详见Byte源码。
       11: astore_2                // 将栈顶数值赋值给局部变量表中下标为2的引用型局部变量b,栈顶数值出栈。此时b为127
      12: aload_0                // 局部变量表中下标为0的引用型局部变量进栈,即this,加载this主要是为了下面通过this调用add方法。  
       13: aload_1                // 局部变量表中下标为1的引用型局部变量a进栈  
       14: invokevirtual #2      // 自动拆箱,访问栈顶元素a,调用实例方法a.byteValue获取a所指Byte
                                 // 对象的value值127,并压栈
       17: iconst_1                // int型常量值1进栈
       18: iadd                    // 依次弹出栈顶两int型数值1(0000 0001)、127(0111 1111)
                                 //(byte类型自动转型为int类型)相加,并将结果128(1000 0000)进栈
      19: i2b                    // 栈顶int值128(1000 0000)出栈,强转成byte值-128(1000 0000),并且结果进栈
       20: invokestatic  #3      // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
                                 // 返回value值为-128的Byte对象的地址,并压栈
       23: dup                    // 复制栈顶数值,并且复制值进栈
       24: astore_1                // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量a,栈顶数值出栈。此时a为-128
       25: invokevirtual #4      // 调用实例方法add:(Byte),传入的实参为栈顶元素,也即a的拷贝,前面已经分析过了,该调用不改变a的对象值
                                 // 该实例方法的调用需要访问栈中的两个参数,一个是实参,也即a的拷贝,一个是在第12步入栈的this。
      28: aload_0                // 局部变量表中下标为0的引用型局部变量进栈,即this,加载this主要是为了下面通过this调用add方法。
      29: aload_2                // 局部变量表中下标为2的引用型局部变量b进栈  
       30: invokevirtual #4      // 调用实例方法add:(Byte),传入的实参为栈顶元素,也即b,前面已经分析过了,该调用不改变b的对象值
                                 // 该实例方法的调用需要访问栈中的两个参数,一个是实参,也即b,一个是在第28步入栈的this。
       33: return                // 函数执行到最后,b所指对象的值没有改变,仍为127。
 }
  1. 综合以上分析,原问题的输出为-128 127

  2. 小结:
    通过以上分析,我们发现该题综合考察了Byte自动拆/装箱、Byte对象缓存、Java编译器对i=i++的特殊处理等等,相当有难度呀。

i=0;i=i++为什么等于0这个问题困扰了我好长的一段时间,结果前段时间还试图从虚拟机那个层面进行解释,但无论是线程还是方法调用都不能解释其现象,发现方向性错误,这只是一个语言的特性而已。在java lang spec中提到:
1、java运算符的优先级++符是大于=的。
2、The result of the postfix increment expression is not a variable, but a value.后++符表达式的结果是个值而不是一个变量。
也就是说后++符先将自己的值存储起来,然后对变量进行++;
再进行赋值操作,也就是将先存储起来的值赋给变量i,这样的操作就导致了i值被置为0了

对于C和C++来说不一样,在讲到m=i++操作时,C语言是先将i的值赋给了m,然后将i值++,这样i=i++的结果自然就是1了,c的实现中是不存在那个中间的值的存储的。

由于java和c不同的语言特性,导致了i=i++的不同之处,前面的笔记中已经提到,由于java lang spec中的一些细微规定,导致其运行结果的不同,我们可以用个例子来看i=i++在jvm中实际的运行过程。
源程序test.java:
public class test {
public test() {
}
public static void main(String[] args) {
int i=0;
i=i++;
}

}
我们用javap来看其实际的虚拟机指令集:
C:\JBuilderX\jdk1.4\bin>javap -c -classpath “d:/” test
Compiled from “test.java”
public class test extends java.lang.Object{
public test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object.”“:()V
4: nop
5: return

public static void main(java.lang.String[]);
Code:
0: iconst_0 //常数0入栈
1: istore_1 //i赋值,常数值出栈
//至此完成i=0;
2: iload_1 //装载变量i,0入栈
//第2步是特殊的一步,这步将i值先行保存,以备赋值使用
3: iinc 1, 1 //变量值增加,栈内值不变
//至此完成i++
6: istore_1 //i赋值,0出栈。
//至此完成i=i++
7: nop //donothing
8: return

}

对比而言,对于i++而言,i=i++指令多了两步,2和6
其实这两步是赋值符号引起的,有意思的是第二步出现的时机,是在iinc之前,这就是因为java lang spec中规定的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值