2009-08-14 17 views
6

Al hacer algunas cosas que no son realmente extravagantes con Java, me vino un error con los genéricos que no pude entender por qué no funciona. El código es:Error del compilador de genéricos de Java

package test; 
import java.util.*; 
public class TestClass { 
    public static class A extends C{} 
    public static class B extends C{} 
    public static class C{} 
    public static class D<T>{} 
    public static class E<T>{} 

    public static void main(String args[]){ 
     E<D<? extends C>> a = new E<D<A>>(); 
     E<D<? extends Object>> b = new E<D<? extends C>>(); 
     E<D<? extends A>> c = new E<D<A>>(); 
     E<D<? super A>> d = new E<D<A>>(); 
     D<? extends C> e = new D<A>(); 
     D<? extends A> f = new D<A>(); 
     D<? extends A> g = new D<A>(); 
    } 
} 

El error que consigo al compilar es:

 
test/TestClass.java:11: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> a = new E>(); 
          ^
test/TestClass.java:12: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> b = new E>(); 
           ^
test/TestClass.java:13: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> c = new E>(); 
          ^
test/TestClass.java:14: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> d = new E>(); 
         ^
4 errors 

Si E<D<? extends C>> se encuentra, que seguramente debe coincidir con E<D<? extends Object>>, ¿verdad? ¿O me he perdido algo?

+3

esta es una buena pregunta para la próxima edición de Puzzlers Java http://www.javapuzzlers.com/ – dfa

+0

Creo que te has topado con un caso extremo. Muy interesante. –

+0

Esto puede ser útil si puede darle sentido: http://bit.ly/3RrNV3 – teabot

Respuesta

1

Compruebe el tipo de objeto devuelto por la llamada Arrays.asList. Me gustaría Supongo que devuelve una lista < D <? extiende el objeto C > >, que no se convertirá en una lista < D <? extiende el objeto > >.

+0

Mire la nueva línea que acabo de publicar. ¿Y por qué eso no es moldeable? – arnebef

+0

Todo se reduce a lo que comenté en la respuesta de GrzegorzOledzki. Las referencias le permitirían usar una gama más amplia de clases que su clase de genéricos original acepta. – Zed

2

Tal vez esto ayudaría a entender:

ArrayList<Object> aList = new ArrayList<String>(); 

Esto no compila ya sea con un error similar.

EDIT: Consulte con: http://www.ibm.com/developerworks/java/library/j-jtp01255.html

+0

Esto no se compila, porque al usar la referencia aList, podría insertar Objetos en su lista, pero se suponía que solo contenía cadenas. – Zed

+2

Ese es su punto. –

1

<? extends C> especifica límites superiores del tipo, lo que significa el tipo tiene que ser extendido de C. <? extends Object> es aparentemente más general, por lo que es incompatible.

Piensa en este caso de uso. Al especificar un límite superior, espera que se implemente cierta interfaz mínima. Supongamos que tiene un método doIt() declarado en C. Cualquier clase que se extienda C tendría ese método, pero no todas las clases que amplían el objeto (es decir, todas las clases en Java).

+0

Lo has equivocado, intento asignar a arnebef

+0

Es posible que tenga que pensar de manera incorrecta :) Esto es exactamente lo opuesto a la seguridad del tipo de asignación de objetos. –

+0

Ninguno funcionaría, tenga sentido o no. Los tipos genéricos tienen que coincidir exactamente, no solo extendiendo tipos. – Jorn

0

Es peor que eso. Ni siquiera se puede hacer esto:

List<D<?>> lDQ = Arrays.asList(new D<A>()); 

Es más clara si cambia de programa de la siguiente manera:

List<D<? extends C>> a = Arrays.asList(new D<A>(), new D<B>()); //compiles 
List<D<? extends Object>> b = a; //error 

Básicamente, usted está declarando la b como algo que puede aceptar contenidos generales (D<cualquier cosa>), pero la lista que le asigna es algo que solo acepta contenidos más específicos (D<superclase común más cercana de A y B>).

Declarar b como List<D<? extends Object>> implica que usted podría, por ejemplo, b.add(new D<String>()), pero en realidad es un List<D<? extends C>>, por lo que no se puede.

2

Esto es básicamente el mismo caso que publicado anteriormente. Básicamente, en un caso de los genéricos, que nunca están permiso para realizar esta assigment:

parece este ejemplo:

ArrayList<Object> alist = new ArrayList<Number>(); 

Esto no se compila porque no es un tipo seguro. Posiblemente podría agregar Strings aList.Está intentando asignar una lista de objetos que están garantizados como números, pero puede ser cualquier número, a una lista que solo le garantiza que contenga objetos, pero que puede ser cualquier objeto. Si el compilador permitió este caso, se aflojaría la restricción sobre qué tipos de objetos pueden entrar en la lista. Esto es por qué debe utilizar el comodín, tales como:?

ArrayList<? extends Object> alist = new ArrayList<Number>(); 

Para el compilador ArrayList<? extends Object>, significa "un ArrayList de algún tipo específico '?' que no sé, pero que sé que extiende Object. ¿Está garantizado que este ArrayList solo contiene elementos de este desconocido? tipo, y por lo tanto contiene solo objetos ". En este caso, el compilador no le permitirá hacer alist.add (2). ¿Por qué es ese el caso, porque el compilador no conoce el tipo de los elementos de la lista y no puede garantizar que se le permita insertar objetos enteros en él?

Tiene razón al pensar que D<? extends Object> es un supertipo de D<? extends C>. Sin embargo, List<D<? extends Object>> no es un subtipo de List<D<? extends C>>, debe usar List<? extends D<? extends C>>.

Su caso es básicamente equivalente a

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); 

Usted tiene el mismo problema que el anterior, la lista en el lado derecho sólo puede contener objetos de la clase D cuyo parámetro tipo es C, y que está tratando de asignarlo a una lista (en el lado izquierdo) puede contener objetos de la clase D cuyo tipo de parámetro puede ser cualquier objeto.

De modo que si el compilador permitía que su código no fuera seguro, y lo siguiente fallaría.

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); //< not type safe 
alist.add(new D<Number>); //< oops 

En resumen, lo que necesita para su ejemplo específico es el siguiente:

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends Object>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is identical 
List<D<? extends C>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends C>> c = Arrays.asList(new D<A>()); 

// type parameter of left hand side is identical 
List<D<A>> c = Arrays.asList(new D<A>()); 

Espero que esto ayude.

0

El hecho es que List<D<? extends Object>> básicamente define una nueva clase y también lo hace List<D<? extends C>>. Incluso cuando c extiende Object no significa que List<D<? extends C>> se extiende a List<D<? extends Object>> y que se necesitaría para que la asignación funcione.

Althoug este series of articles está escrito para la plataforma .NET de la descripción del problema sigue siendo válida para los genéricos de Java

+0

Necesita escapar de los corchetes angulares con palos de retroceso. –

0

No se puede heredar en el genéricos parámetros. Supongamos que tiene las siguientes referencias:

List<Object> a; 
List<String> b; 

Ahora asigna las dos listas: a = b;

Si algún código hace lo siguiente:

a.add(new Integer(1)); 

¿Qué pasa si alguien hace lo siguiente:

String s = b.get(0); 

Usted recibirá un número entero de una lista de cadenas. Eso no debería funcionar. Es por eso que Lista y Lista son incompatibles, aunque una Cadena se puede asignar a una Referencia de objeto. Los genéricos no funcionan con herencia.

-1

No.
<?extiende C > no es lo mismo que <? expande Object >
Si ese fuera el caso, sería el plomo también el supuesto (sin especificar) - C parte del objeto, y el plomo que decir ...

class C{ public void doSomethingMEaningful(); };

List< ? extends C > allAtSea = new ArrayList<...>(); List< ? extends Object > allObjects = new ArrayList<...>(); allObjects.add(new Integer(88)); ...

allAtSea.addAll(allObjects); ...

allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. this finds the Integer 88


El C++ FAQ proporciona un ejemplo lúcido esto en 21.3

+0

No creo que un ejemplo de C++ ayude en una pregunta de Java. – Jorn

+0

Me permito diferir, si la lista de algo es lo mismo que una lista-de-algo-otro se trata de entender la herencia y la sustituibilidad.
Eso, IMO, es sobre los fundamentos de OO; por lo tanto, la referencia a C++ debe ser aceptable – Everyone

+0

El ejemplo aún no es correcto (o incluso ayuda, IMO). La línea allAtSea.addAll() no se compila, y tampoco lo hace allObjects.add(). – Jorn

0

Creo que esto será más claro si extendemos su ejemplo. Vamos a poner algunas funciones en E y elaborado en la primera línea de su principal método:

public static class E<T>{ 
    private final T thing; 

    public void setThing(T thing) { 
     this.thing = thing; 
    } 

    public T getThing() { 
     return thing; 
    } 
} 

public static void main(String[] args) { 
    E<D<? extends C>> a1; 
    E<D<A>> a2 = new E<D<A>>(); 
    a1 = a2; // this won't compile, but why? 

    // these things are permissible on a1: 
    a1.setThing(new D<A>()); 
    a2.setThing(new D<B>()); 

    // now let's try doing the same thing to a2: 
    a2.setThing(new D<A>()); 
    a2.setThing(new D<B>()); // oops 
} 

La última línea del nuevo método principal es la razón por A1 no puede ajustarse a a2. Si el compilador le permitió hacer eso, entonces podría poner un D <B> en un contenedor que se declaró que solo contenía D <A> s.

La razón de que esto no es obvio a partir de su ejemplo original es porque no crea una variable independiente que hace referencia el objeto con la E más restrictiva < D <Un> > escribir. Pero espero que ahora pueda ver que si existe tal referencia, su tipo de seguridad se vería comprometida por la asignación que intentó hacer.

1

Al asignar a una variable (E<T>) con un no-comodín tipo genérico T, el objeto de ser asignado debe tener exactamente T como su tipo genérico (incluyendo todos los parámetros de tipo genérico de T, comodín y no comodín). En su caso T es D<A>, que no es del mismo tipo que D<? extends C>.

Lo que puede hacer, porque D<A> es asignable a D<? extends C>, es utilizar el tipo de comodín:

E<? extends D<? extends C>> a = new E<D<A>(); 
+0

Creo que esta respuesta explica lo mejor de todo. Puedes hacer E > e = new E >(); pero no E > e = nuevo E >(); – Jorn

Cuestiones relacionadas