泛型,数组和ClassCastException

时间:2008-12-16 18:42:51

标签: java arrays generics classcastexception

我认为这里肯定会有一些我不知道的微妙内容。请考虑以下事项:

public class Foo<T> {
  private T[] a = (T[]) new Object[5];

  public Foo() {
    // Add some elements to a
  }

  public T[] getA() {
    return a;
  }
}

假设您的main方法包含以下内容:

Foo<Double> f = new Foo<Double>();
Double[] d = f.getA();

您将收到CastClassException,但java.lang.Object邮件无法投放到java.lang.Double

谁能告诉我为什么?我对ClassCastException的理解是当你试图将一个对象转换为一个无法转换的类型时抛出它。也就是说,它不是一个实例的子类(引用文档)。 e.g:

Object o = new Double(3.);
Double d = (Double) o; // Working cast
String s = (String) o; // ClassCastException

似乎我能做到这一点。如果a只是T而不是数组T[],我们可以获得a并毫无问题地投射它。为什么数组会破坏它?

感谢。

3 个答案:

答案 0 :(得分:20)

Foo<Double> f = new Foo<Double>();

当您使用此版本的泛型类Foo时,对于成员变量a,编译器基本上采用这一行:

private T[] a = (T[]) new Object[5];

并将T替换为Double以获取此信息:

private Double[] a = (Double[]) new Object[5];

你不能从Object转换为Double,因此是ClassCastException。

更新和澄清:实际上,在运行一些测试代码之后,ClassCastException比这更微妙。例如,这个main方法可以正常工作,没有任何例外:

public static void main(String[] args) {
    Foo<Double> f = new Foo<Double>();
    System.out.println(f.getA());
}

当您尝试将f.getA()分配给Double[]类型的引用时,会出现此问题:

public static void main(String[] args) {
    Foo<Double> f = new Foo<Double>();
    Double[] a2 = f.getA(); // throws ClassCastException
    System.out.println(a2);
}

这是因为有关成员变量a的类型信息在运行时被删除。泛型只在编译时提供类型安全性(我在初始帖子中忽略了这一点)。所以问题不在于

private T[] a = (T[]) new Object[5];

因为在运行时这个代码真的是

private Object[] a = new Object[5];

当方法getA()的结果(在运行时实际返回Object[])被分配给类型为Double[]的引用时,会出现问题 - 此语句抛出ClassCastException,因为Object不能被投到双。

更新2 :回答你的最后一个问题“为什么阵列会破坏这个?”答案是因为语言规范不支持通用数组创建。 See this forum post for more - 为了向后兼容,在运行时对T的类型一无所知。

答案 1 :(得分:5)

@ mattb的解释可能会有一些小错误。

错误

  

java.lang.Object无法强制转换为java.lang.Double。

是:

  

[Ljava.lang.Object;无法转换为[Ljava.lang.Double

[L表示阵列。也就是说,错误是对象的数组无法强制转换为Double数组。这与以下情况相同:

Object[] oarr = new Object[10];
Double[] darr = (Double[]) oarr;

显然不允许这样做。

关于创建类型安全数组的问题,另一个选择是除了init中的类对象并使用Array.newInstance:

import java.lang.reflect.Array;

class Foo<T> {
  private T[] a ;

  public Foo(Class<T> tclass) {
    a = (T[]) Array.newInstance(tclass, 5);
  }

  public T[] getA() {
    return a;
  }

  public static <T> Foo<T> create(Class<T> tclass) {
    return new Foo<T>(tclass);
  }
}

class Array1
{
  public static final void main(final String[] args) {
    Foo<Double> f = Foo.create(Double.class);
    Double[] d = f.getA();
  }


}

答案 2 :(得分:2)

@matt b:谢谢你的回答!非常有帮助。

我找到了感兴趣的解决方法:给getA方法一个初始化的数组来填充。这样就可以获得类型信息。

public class Foo<T> {
  private T[] a = (T[]) new Object[5];

  public Foo() {
    // Add some elements to a
  }

  public T[] getA(T[] holdA) {
    // Check whether holdA is null and handle it...then:
    holdA = (T[]) Array.newInstance(holdA.getClass().getComponentType(), a.length);
    System.arraycopy(a, 0, holdA, 0, a.length);
    return holdA;
  }
}

然后为您的主要方法:

public static void main(String[] args) {
  Foo<Double> f = new Foo<Double>();
  Double[] a2 = new Double[1];
  a2 = f.getA(a2);
}