JAVA奇妙的类型转换

来源:互联网 发布:知乎图标 编辑:程序博客网 时间:2024/05/03 03:13

------- android培训、java培训、期待与您交流! ----------注意:下面的所有内容在java和C#中均适用。二者唯一的不同就是java字节码与.NET IL指令的形式。

首先看看看一下代码,猜测运行结果是什么,结局肯定出乎你的意料

package MyPackage;public class TypeCast {public static void main(String[] args) {byte a=12;byte b=13;byte c=a+b;}}

        这段代码无论在java中,还是C#中,都会给一个类型转换的编译错误:“无法将int类型隐式转换为byte类型”。从这个错误提示中我们可以得出如下结论:a+b结果变成了一个int类型。由于不能将这个int类型隐式转换为byte类型,所以c=a+b;才会出现无法将int类型转换为byte类型的错误。

       可问题是, 两个byte类型相加,结果应该还是byte,怎么会成为int呢?这个问题在我最初学习C#的时候就已经遇到了。
        要想深入理解这个问题,还是需要从java的虚拟机或者.net的CLR说起。由于这是一篇关于java的日志,所以下面以java虚拟机为例:

        在32位java虚拟机中,最基本的数据单元是word,简称字。一个字大小被称作字长,32位java虚拟机的字长是4字节。也就是说,在java虚拟机中,每四个字节为一个单位,如果你存储一个byte类型,虽然byte大小事1字节,但他也要占用4字节,byte类似在被存储到虚拟机的那一刻会被转换为int。同样,如果存储一个short类型,他也会占用4字节,并且隐式转换为int类型。

       为了更形象的说明这个问题,可以将java虚拟机中的内存想象成一个个窗格,每个窗格大小是4个字节。如果你存储一个小于4字节的类型,也要占用4字节。如果你存储一个大于4字节的类型,那么就需要占用多个窗格。这个窗格就是java虚拟机中的字,窗格的大小就是字长。如下图

 

        现在就可以解释上面的代码中为什么不能‌编译了,在byte类型被装入虚拟机的时候, 会被转换为int类型,所以变量a和b都被转换为了int,接下来执行的就是两个int类型的加法,结果毫无疑问是个int类型。如果想把这个int类型赋值给一个byte类型变量,就需要显示转换,否则就会出现一个“无法将int类型隐式转换为byte类型”的编译错误。

     

现在我们加上一个显示转换,如下:

package MyPackage;public class TypeCast {public static void main(String[] args) {byte a=12;byte b=13;byte c=(byte)(a+b);}}


   编译运行,结果为25;

   为了更深刻的理解虚拟机如何处理类型转换,我们将反汇编一下,反汇编后的代码如下:


   接下来我一行一行的解释这段字节码。
   第1行:bipush 12 将byte类型的12转为int类型后,压入操作数栈。bipush中的b为byte的首字母,i为int的首字母,意味将byte隐式转换为int。注意,这时候,12就已经成为int类型了,占4字节大小。
   第2行:istore_1 将操作数栈顶元素12弹出,存储到局部变量a中。
   这两行字节码对应于源代码中的byte a=12;完成赋值操作。
   第3行:bipush 13;
   第4行:istore_2
   这两行代码对应于源代码中的byte b=13;
   第5行:iload_1;将a压入操作数栈。
   第6行:iload_2; 将b压入操作数栈;。
   第7行:iadd;弹出操作数栈‌顶的两个元素,执行int类型的加法操作,将结果再压入栈中。注意,这时候结果是int类型值。
   第8行:i2b;将栈顶元素弹出,转换为byte类型;然后再压入操作数栈中。
   第9行:istore_3弹出栈顶元素,赋值给c;
   第10行:return 返回。

   这里最重要的地方有如下几处:

    第一行bipush 12指令,这个指令将值12作为int类型压入栈。从这一刻开始,就已经不存在byte类型了。接下来第2行指令istore_2将这个栈顶元素赋值给变量a。请注意,变量a占用4个字节,他被隐式转换为int类型存放在栈的局部变量区了。

  第7行iadd指令,该指令执行加法操作 ,结果是一个int类型。

  第8行i2b指令,这个指令执行转换操作,将int类型转为byte类型,然后再将转换后的值压入栈。第9行,赋值给变量c;


   接下来介绍一些有趣的事情,将一个long类型转换为short类型,java虚拟机是如何工作的。看如下代码:

package MyPackage;public class TypeCast {public static void main(String[] args) {long a=12;long b=13;byte c=(byte)(a+b);}}



   用javap反编译之后代码如下:



       0:ldc2_w        #16  这条指令比较复杂,简而言之,就是将值12压入操作数栈。
       3: lstore_1           弹出操作数栈顶元素值12,赋值给变量a。
       4: ldc2_w        #18  将值13压入操作数栈。
       7: lstore_3           弹出操作数栈顶元素值13,赋值给变量b。
       8: lload_1            将变量a压入操作数栈。
       9: lload_3            将变量a压入操作数栈。
      10: ladd               弹出栈顶的两个元素,执行long类型的加法运算,将结果再压入栈。
      11: l2i                弹出栈顶元素,转换为int类型,将转换后的值再次压入栈。     
      12: i2b                弹出栈顶元素,转换为byte类型,将转换后的值再次压入栈。  
      13: istore        5    弹出操作数栈顶元素值25,赋值给变量c。
      15: return   

     这里的关键是l2i和i2b指令,这两个指令完成了从long类型到byte类型的转换。需要注意的是,在java虚拟机中以及.netCLR中,没有一个直接的指令用于将long类型转换为byte类型,只能先将long类型转换为int类型,然后在将int类型转换为byte类型。可能会造成产生一定的性能损失。









原创粉丝点击