从字节码角度分析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++的特殊处理等等,相当有难度呀。
相关文章推荐
- Android ——fragment
- IOS-文档1
- 书单
- android使用smb协议在线播放视频(二)
- 关于C语言中的cons的使用。
- 一份比较详细的DOS命令说明 (有%1参数说明)
- 10个帮程序员减压放松的网站
- 冒泡排序
- Android源码环境配置之Ubuntu 14.04 LTS x64
- UINavigaionController中的传值(协议)
- Android 网络流量监听开源项目-ConnectionClass源码分析
- leetcode每日解题思路 221 Maximal Square
- Openstack国内商业模式搞笑版
- Oracle 11g新特性触发Direct Path Read 等待事件的一个案例
- Exchange工具06—What is my ip
- RHEL7.0启动流程
- kafka快速入门
- 程序包管理(RPM,yum与编译安装)
- 设计包含min函数的栈
- 最小生成树-MST算法详解及代码实现