Generics, arrays, and the ClassCastException

时光总嘲笑我的痴心妄想 提交于 2019-11-27 01:52:08
Foo<Double> f = new Foo<Double>();

When you use this version of the generic class Foo, then for the member variable a, the compiler is essentially taking this line:

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

and replacing T with Double to get this:

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

You cannot cast from Object to Double, hence the ClassCastException.

Update and Clarification: Actually, after running some test code, the ClassCastException is more subtle than this. For example, this main method will work fine without any exception:

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

The problem occurs when you attempt to assign f.getA() to a reference of type Double[]:

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

This is because the type-information about the member variable a is erased at runtime. Generics only provide type-safety at compile-time (I was somehow ignoring this in my initial post). So the problem is not

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

because at run-time this code is really

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

The problem occurs when the result of method getA(), which at runtime actually returns an Object[], is assigned to a reference of type Double[] - this statement throws the ClassCastException because Object cannot be cast to Double.

Update 2: to answer your final question "why do arrays break this?" The answer is because the language specification does not support generic array creation. See this forum post for more - in order to be backwards compatible, nothing is known about the type of T at runtime.

There may be some small errors in @mattb's explanation.

The error is not

java.lang.Object cannot be cast to java.lang.Double.

It is:

[Ljava.lang.Object; cannot be cast to [Ljava.lang.Double

The [L means an array. That is, the error is that an array of Objects cannot be cast to an array of Double. This is the same case as following:

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

This is obviously not allowed.

For your issue of creating typesafe arrays, another alternative is to except a class object in init and use 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();
  }


}

@matt b: Thanks for the answer! Very helpful.

I have found a workaround for those interested: give the getA method an initialized array to populate. That way the type info is available.

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;
  }
}

Then for your main method:

public static void main(String[] args) {
  Foo<Double> f = new Foo<Double>();
  Double[] a2 = new Double[1];
  a2 = f.getA(a2);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!