为什么Object obj = new Integer(2);字节b =(字节)obj;在运行时导致ClassCastException?

时间:2017-12-22 15:30:45

标签: java casting boxing

为什么第二行在运行时抛出ClassCastException

Object obj = new Integer(2);
Byte b2 = (byte) obj; // at Runtime: ClassCastException "Integer cannot be cast to Byte"

我认为Integer(引用obj指向的)被取消装入int,然后转换为byte,然后装入Byte并成功分配。 Eclipse编译器警告也说(稍微纠正一下):

  
      
  • Object类型的表达式取消装入字节
  •   
  • 类型字节的表达式被装入字节
  •   

那么为什么它不能将RHS中的这个字节分配给LHS中的字节引用?

P.S。 Oracle javac编译器上的ClassCastException相同

如果Object obj = new Integer(2);,则Long b2 = (long) obj;有效但Long b2 = 7;失败。虽然Byte b2 = (byte) obj;失败了,但Byte b2 = 7;没问题!也许这些相互差异有一些线索?

根据经验,我会说在拆箱后禁止缩小原始类型(即使使用显式转换)(允许加宽) - 它可以解释这种行为。

终于明白了:

5.2。作业上下文(JLS): variable = expression

  

分配上下文允许使用以下之一:

     

身份转换(第5.1.1节)

     

扩大原始转换(第5.1.2节)

     

扩大参考转换(第5.1.5节)

     

拳击转换(§5.1.7),可选地后跟加宽   参考转换

     

取消装箱转化(第5.1.8节)可选地后跟a   扩展原始转换。

     

此外,如果表达式是常量表达式(§15.28)   类型为byte,short,char或int:

     

缩小原始转换,然后进行装箱转换   如果变量的类型为:

,则可以使用      

字节,常量表达式的值可以在字节类型中表示。

     

,常量表达式的值可以在short类型中表示。

     

字符,常量表达式的值可在char类型中表示。

但这在运行时也很好:

 Object o = new Integer(2);
 Integer i2 = (Integer) o; 
 Integer i3 = (int) o; 

我们需要进一步阐述“分配上下文”与“分配上下文” - 它们如何协同工作。

2 个答案:

答案 0 :(得分:3)

原始代码

Object obj = new Integer(2);
Byte b2 = (byte) obj;

装箱/取消装箱是静态,即在编译时确定。您已投放到Object,因此编译器并不知道obj实际上属于Integer类型。相反,它生成假定Byte实例的字节码,以及显式检查(在运行时失败):

ALOAD 1
CHECKCAST java/lang/Byte                                // Oh dear
INVOKEVIRTUAL java/lang/Byte.byteValue ()B              // Unbox as a Byte
INVOKESTATIC java/lang/Byte.valueOf (B)Ljava/lang/Byte; // Box as a Byte
ASTORE 2

<小时/>

因此,让我们使用Integer引用

Integer obj = new Integer(2);
Byte b2 = (byte) obj;

第二行甚至无法编译。 (byte) obj投射上下文,  并且有bunch of rules定义了此处允许的内容。 1 不允许进行拆箱转换,然后进行缩小转换。

<小时/>

因此,让我们使用扩展转换

规则允许进行拆箱转换,然后进行扩展转换,因此此代码编译并运行时没有错误:

Integer obj = new Integer(2);
Long b2 = (long) obj;

让我们看看相应的字节码:

ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I            // Unbox as an Integer
I2L                                                     // Convert to long
INVOKESTATIC java/lang/Long.valueOf (J)Ljava/lang/Long; // Box as a Long
ASTORE 2

我们看到两个不同之处:

  1. 它正确地取消装箱为Integer而不是Byte
  2. 没有CHECKCAST,因为编译器确切知道存在什么类型。
  3. 所以它实际上更有效率!

    <小时/>

    但我想要原始的预期行为!

    那么,你别无选择,只能执行相关的演员阵容:

    Object obj = new Integer(2);
    Byte b2 = (byte)(int)(Integer) obj;
    

    为了完整起见,这里是相应的字节码:

    ALOAD 1
    CHECKCAST java/lang/Integer                             // This is now ok
    INVOKEVIRTUAL java/lang/Integer.intValue ()I            // Unbox as an Integer
    I2B                                                     // Convert to byte
    INVOKESTATIC java/lang/Byte.valueOf (B)Ljava/lang/Byte; // Box as a Byte
    ASTORE 2
    

    <子> 1。请注意,由于= has its own set of rules,此处还有分配上下文。除此之外,人们不能Long b2 = 7;,因为没有任何东西允许扩大转换,然后进行拳击转换。

答案 1 :(得分:1)

Casts在Java中不起作用。如你所见,两个包装类之间的转换会失败 - 它不会通过拆箱,扩展和重新装箱。如果你想实现这种行为,你必须自己完成这些步骤:

Object obj = new Integer(2);
Byte b2 = (byte) ((Integer)obj).intValue();