Generics, arrays, and the ClassCastException

后端 未结 3 945
长情又很酷
长情又很酷 2020-11-27 06:45

I think there must be something subtle going on here that I don\'t know about. Consider the following:

public class Foo {
  private T[] a = (T[]) ne         


        
相关标签:
3条回答
  • 2020-11-27 07:30
    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.

    0 讨论(0)
  • 2020-11-27 07:41

    @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);
    }
    
    0 讨论(0)
  • 2020-11-27 07:42

    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();
      }
    
    
    }
    
    0 讨论(0)
提交回复
热议问题