您的位置:首页 > 其它

从字节码角度分析Byte类型变量b++和++b

2015-09-03 16:37 197 查看

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

public class Test2
{
public void add(Byte b)
{
b = b++;
}
public void test()
{
Byte a = 127;
Byte b = 127;
add(++a);
System.out.print(a + " ");
add(b);
System.out.print(b + "");
}
}

2. 为方便分析起见,将打印的语句去掉,如下:

public void add(Byte b)
{
b = b++;
}
public void test()
{
Byte a = 127;
Byte b = 127;
add(++a);
add(b);
}

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
}

 4. 字节码很长,看着发怵,不用怕,我们将字节码分成两部分:add方法和test方法。

 5. 我们先来看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++;分为三步:参考:https://www.geek-share.com/detail/2391017240.html

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

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

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

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

 6. 搞懂了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。
}

 

7. 综合以上分析,原问题的输出为-128 127

8. 小结:

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

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: