Generics, arrays y ClassCastException

Creo que debe haber algo sutil aquí que no sepa. Considera lo siguiente:

public class Foo { private T[] a = (T[]) new Object[5]; public Foo() { // Add some elements to a } public T[] getA() { return a; } } 

Supongamos que su método principal contiene lo siguiente:

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

Obtendrás una CastClassException con el mensaje java.lang.Object no se puede convertir a java.lang.Double .

puede alguien decirme por que? Mi comprensión de ClassCastException es que se lanza cuando intentas convertir un objeto a un tipo que no se puede convertir. Es decir, a una subclase de la cual no es una instancia (para citar la documentación). p.ej:

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

Y parece que puedo hacer esto. Si a era solo una T lugar de una matriz T[] , podemos obtener a y emitirla sin problemas. ¿Por qué las matrices rompen esto?

Gracias.

 Foo f = new Foo(); 

Cuando utilizas esta versión de la clase genérica Foo, para la variable miembro a , el comstackdor básicamente toma esta línea:

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

y reemplazando T con Double para obtener esto:

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

No se puede convertir de Object a Double, de ahí la ClassCastException.

Actualización y aclaración: en realidad, después de ejecutar algún código de prueba, ClassCastException es más sutil que esto. Por ejemplo, este método principal funcionará bien sin excepción:

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

El problema ocurre cuando intenta asignar f.getA() a una referencia de tipo Double[] :

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

Esto se debe a que la información de tipo sobre la variable miembro a se borra en el tiempo de ejecución. Los generics solo proporcionan seguridad de tipo en tiempo de comstackción (de alguna manera, no hice caso de esto en mi publicación inicial). Entonces el problema no es

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

porque en tiempo de ejecución este código es realmente

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

El problema ocurre cuando el resultado del método getA() , que en tiempo de ejecución realmente devuelve un Object[] , se asigna a una referencia de tipo Double[] – esta instrucción arroja la ClassCastException porque Object no se puede convertir a Double.

Actualización 2 : para responder a su pregunta final “¿por qué las matrices rompen esto?” La respuesta es porque la especificación del lenguaje no es compatible con la creación de matriz genérica. Consulte esta publicación en el foro para obtener más información : para ser compatible con versiones anteriores, no se sabe nada sobre el tipo de T en tiempo de ejecución.

Puede haber algunos pequeños errores en la explicación de @ mattb.

El error no es

java.lang.Object no se puede convertir a java.lang.Double.

Es:

[Ljava.lang.Object; no se puede convertir a [Ljava.lang.Double

La [L significa una matriz. Es decir, el error es que una matriz de Objetos no se puede convertir en una matriz de Doble. Este es el mismo caso que sigue:

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

Esto obviamente no está permitido.

Para su problema de crear matrices de tipo seguro, otra alternativa es exceptuar un objeto de clase en init y usar Array.newInstance:

 import java.lang.reflect.Array; class Foo { private T[] a ; public Foo(Class tclass) { a = (T[]) Array.newInstance(tclass, 5); } public T[] getA() { return a; } public static  Foo create(Class tclass) { return new Foo(tclass); } } class Array1 { public static final void main(final String[] args) { Foo f = Foo.create(Double.class); Double[] d = f.getA(); } } 

@matt b: ¡Gracias por la respuesta! Muy útil.

He encontrado una solución para los interesados: dar al método getA una matriz inicializada para rellenar. De esa forma, la información de tipo está disponible.

 public class Foo { 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; } } 

Entonces para su método principal:

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