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

Why Object obj = new Integer(2); Byte b = (byte) obj; results in ClassCastException in Runtime?

为什么第二行在运行时抛出 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 编译器警告也说(稍微纠正一下):

  • The expression of type Object is unboxed into byte
  • The expression of type byte is boxed into Byte

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

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):变量=表达式

Assignment contexts allow the use of one of the following:

an identity conversion (§5.1.1)

a widening primitive conversion (§5.1.2)

a widening reference conversion (§5.1.5)

a boxing conversion (§5.1.7) optionally followed by a widening reference conversion

an unboxing conversion (§5.1.8) optionally followed by a widening primitive conversion.

In addition, if the expression is a constant expression (§15.28) of type byte, short, char, or int:

A narrowing primitive conversion followed by a boxing conversion may be used if the type of the variable is:

Byte and the value of the constant expression is representable in the type byte.

Short and the value of the constant expression is representable in the type short.

Character and the value of the constant expression is representable in the type char.

但这在运行时也很好:

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

我们需要进一步探讨 "assignment context" 与 "assignment context" - 它们如何协同工作。

原代码

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

Boxing/unboxing 是 静态 确定的,即在编译时。您已转换为 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 不允许在拆箱转换后进行 narrowing 转换。


所以让我们改用扩大转换

规则确实允许在 widening 转换之后进行拆箱转换,因此此代码编译和运行没有错误:

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,因为编译器肯定知道存在什么类型。

所以它实际上更有效率!


但我想要最初预期的行为!

那么你别无选择,只能执行相关的链式转换:

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; 因为没有任何东西允许扩大转换然后进行装箱转换。

转换在 Java 中不是那样工作的。如您所见,两个包装器 类 之间的转换只会失败 - 它不会经历拆箱、加宽和重新装箱。如果您想实现此行为,您必须自己完成这些步骤:

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