java.lang.Object 的泛型不能转换为 [Ljava.lang.Object

Generics as result of java.lang.Object cannot be cast to [Ljava.lang.Object

任何人都可以解释为什么泛型 <Object[]> 会导致 ClassCastException(RuntimeException!)

我知道在编译阶段删除所有泛型并且对字节码没有任何影响。但它似乎有一些细微差别。

这是我的例子(为此post做了简化):

public class CastTest {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add(new Object());
        List<Object[]> b = a;
        b.get(0).toString();
    }
}

此代码 returns:

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to [Ljava.lang.Object;
    at CastTest.main(CastTest.java:9)

我不明白这段代码有什么问题。有人可以解释这种行为吗?

为什么要将其转换为对象数组列表。

尝试对象列表,它会起作用。

import java.util.ArrayList;
import java.util.List;

public class CastTest {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add(new Object());
        List<Object> b = a;
        b.get(0).toString();
    }
}

也许这样看会更清楚:

import java.util.ArrayList;
import java.util.List;

class Dog { }
class Cat { }

public class CastTest {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add(new Dog());
        List<Cat> b = a;
        Cat c = b.get(0);
    }
}

$ java CastTest
Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to Cat
    at CastTest.main(CastTest.java:12)

如果说泛型不影响字节码是不正确的。如果使用 javap 查看上面的字节码,可以看到在进行赋值之前生成了一个强制转换以确保对象确实是 Cat

  ...
  26: invokeinterface #7,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
  31: checkcast     #8                  // class Cat
  34: astore_3      
  35: return   

如果你真的想要它是一个对象数组列表,你必须添加一个对象数组:

import java.util.ArrayList;
import java.util.List;

public class CastTest {
    public static void main(String[] args) {
        List a = new ArrayList();
        a.add(new Object[]{});
        List<Object[]> b = a;
        System.out.println(b.get(0).toString());
    }
}

$ java CastTest
[Ljava.lang.Object;@65685e30

正如其他人所说,因为 Object 不是 Object[]

短语中有提示java.lang.Object cannot be cast to [Ljava.lang.Object

根据JNI types and Data Structures[Ljava.lang.Object表示:

  • [ - 数组
  • L - 一个 Class

所以java.lang.Object cannot be cast to [Ljava.lang.Object可以读作对象不能转换为对象数组

您是在告诉编译器您要调用 Object[].toString()。这就是编译器生成强制转换 (checkcast) 的原因:

 0: new           #2                  // class java/util/ArrayList
 3: dup
 4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
 7: astore_1
 8: aload_1
 9: new           #4                  // class java/lang/Object
12: dup
13: invokespecial #1                  // Method java/lang/Object."<init>":()V
16: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
21: pop
22: aload_1
23: astore_2
24: aload_2
25: iconst_0
26: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
31: checkcast     #7                  // class "[Ljava/lang/Object;"
34: invokevirtual #8                  // Method java/lang/Object.toString:()Ljava/lang/String;
37: pop
38: return

您可以通过在 Java 代码中自己添加强制转换来防止字节码强制转换:

public static void main(String[] args) {
    List a = new ArrayList();
    a.add(new Object());
    List<Object[]> b = a;
    ((Object) b.get(0)).toString();
}

现在编译器认为不需要强制转换为 Object[],因为您只需要一个 Object 引用。 checkcast 操作码被省略:

 0: new           #2                  // class java/util/ArrayList
 3: dup
 4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
 7: astore_1
 8: aload_1
 9: new           #4                  // class java/lang/Object
12: dup
13: invokespecial #1                  // Method java/lang/Object."<init>":()V
16: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
21: pop
22: aload_1
23: astore_2
24: aload_2
25: iconst_0
26: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
31: invokevirtual #7                  // Method java/lang/Object.toString:()Ljava/lang/String;
34: pop
35: return

这是因为编译器中断了

    b.get(0).toString();

向下进入

    Object[] temp = b.get(0);
    temp.toString();

擦除后转换为 Object[]

现在可以说,编译器可以也选择将其分解为

    Object temp = b.get(0);
    temp.toString();

因为引用仅用于调用在 Object 上声明的 .toString()。如果它这样做了,它就会避免强制转换。但是,为什么编译器会投入额外的分析工作以不同的方式做某事,而只有当你使用错误的类型时才重要?