访问Object []类型的数组时出现ClassCastException

时间:2015-09-19 07:35:40

标签: java

请考虑下面的Java代码(我已经从现实世界中获取了它,但简化了一些并删除了不相关的细节):

public class CastIssue<T> {
  @SuppressWarnings("unchecked")
  private T[] data = (T[]) new Object[1];

  public static void main(final String[] args) {
    final CastIssue<Integer> issue = new CastIssue<>();
    System.out.println(issue.data.length);
  }
}

它编译正常但在执行时抛出以下异常:

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

另一方面,如果我向Object []添加显式强制转换,它的工作无异常:

public class CastIssue<T> {
  @SuppressWarnings("unchecked")
  private T[] data = (T[]) new Object[1];

  public static void main(final String[] args) {
    final CastIssue<Integer> issue = new CastIssue<>();
    System.out.println(((Object[])issue.data).length);
  }
}

为什么在第一种情况下抛出异常?

我使用的是JDK 1.8.0_40

编辑:如@wero所述,编译器在第一种情况下插入一个强制转换。我想理解为什么要插入那些演员(抱歉没有在原始问题中明确说明)。任何对JLS或JVM规范的引用都会很好。

2 个答案:

答案 0 :(得分:0)

在编译的类中,由于类型擦除,字段private T[] data的类型为Object[]。 但是当你访问它时(在你的第一个例子中issue.data.length),它被转换为一个Integer数组。使用javap

反编译该类时很容易看到
  public static void main(java.lang.String[]);
    Code:
       0: new           #1                  // class test/CastIssue
       3: dup
       4: invokespecial #24                 // Method "<init>":()V
       7: astore_1
       8: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1
      12: getfield      #14                 // Field data:[Ljava/lang/Object;
      15: checkcast     #31                 // class "[Ljava/lang/Integer;"
      18: arraylength
      19: invokevirtual #33                 // Method java/io/PrintStream.println:(I)V
      22: return

当您将其强制转换为Object数组(第二个示例)时,checkcast操作将被删除,并且不会发生错误:

  public static void main(java.lang.String[]);
    Code:
       0: new           #1                  // class test/CastIssue
       3: dup
       4: invokespecial #24                 // Method "<init>":()V
       7: astore_1
       8: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1
      12: getfield      #14                 // Field data:[Ljava/lang/Object;
      15: arraylength
      16: invokevirtual #31                 // Method java/io/PrintStream.println:(I)V
      19: return    

答案 1 :(得分:0)

由于类型擦除,T实际上是Object的非静态方法中的CastIssue。在main方法中,T已知为Integer,因此issue.data引用会隐式转换为Integer[]

Integer[]Object[]的子类,同样IntegerObject的子类。

您可以将Integer分配给Object类型的变量,但不能将Object转换/分配给Integer类型的变量。

对于数组也是如此:您可以将Integer[]分配给Object[]类型的变量,但不能将Object[]转换/赋值给{{1}类型的变量}。

那么,你是怎么做的,用泛型?

为了让代码构建Integer[]T的实际实例,必须给出T[]所应的Class的实际T在运行时。

您可以在构造函数中传入该类,并使用反射来创建数组:

private T[] data;

@SuppressWarnings("unchecked")
public CastIssue(Class<T> clazz) {
    this.data = (T[])Array.newInstance(clazz, 1);
}

您的主要方法是:

final CastIssue<Integer> issue = new CastIssue<Integer>(Integer.class);
System.out.println(issue.data.length);

在构造函数上指定Integer两次似乎是多余的,并且确定它可以使用菱形运算符(new CastIssue<>(Integer.class))折叠,但仍然...

您可以使用通用静态方法让编译器从参数中推断出类型(T):

public static <T> CastIssue<T> create(Class<T> clazz) {
    return new CastIssue<T>(clazz);
}

这样主要方法就变成了:

final CastIssue<Integer> issue = create(Integer.class);
System.out.println(issue.data.length);

这对你来说是否更好决定。