2011-05-16 16 views
6

pensé que tengo una buena comprensión de los genéricos de Java.Java comodín comportamiento extraño cuando la clase es genérico

Este código NO COMPILA y sé por qué.

podemos pasar al método de prueba sólo Lista de Tipo de animal o su tipo súper (como la lista de objetos)

package scjp.examples.generics.wildcards; 

import java.util.ArrayList; 
import java.util.List; 

class Animal {} 
class Mammal extends Animal {} 
class Dog extends Mammal {} 

public class Test { 

    public void test(List<? super Animal> col) { 
     col.add(new Animal()); 
     col.add(new Mammal()); 
     col.add(new Dog()); 
    } 

    public static void main(String[] args) { 
     List<Animal> animalList = new ArrayList<Animal>(); 
     List<Mammal> mammalList = new ArrayList<Mammal>(); 
     List<Dog> dogList = new ArrayList<Dog>(); 

     new Test().test(animalList); 
     new Test().test(mammalList); // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Mammal>) 
     new Test().test(dogList); // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Dog>) 

     Dog dog = dogList.get(0); 
    }   
} 

Pero aquí viene la parte extraña (al menos para mí).

si declaramos clase Test como genéricos con sólo añadir <T>, entonces REÚNE! y la arroja java.lang.ClassCastException:

public class Test<T> { 
... 
} 

,

Exception in thread "main" java.lang.ClassCastException: scjp.examples.generics.wildcards.Animal cannot be cast to scjp.examples.generics.wildcards.Dog 

Mi pregunta es por qué la adición genérica clase de tipo T < > (que no se utiliza en cualquier lugar) causada clase para compilar y cambiado el comportamiento de comodín?

Respuesta

7

La expresión new Test() es de tipo cruda. El Java Language Specification defines los tipos de miembros de tipos primas como sigue:

El tipo de un constructor (§8.8), método de instancia (§8.8, §9.4), o no estático campo (§ 8.3) M de un tipo C en bruto que no se hereda de sus superclases o superinterfaces es el borrado de su tipo en la declaración genérica correspondiente a C. el tipo de un miembro estático de un tipo de prima C es el mismo que su tipo en la declaración genérica correspondiente a C.

el borrado de List<? super Animal> es List.

El fundamento de esta definición es probable que los tipos de primas están concebidos como un medio para utilizar los tipos genéricos de código heredado no genérico, donde los parámetros de tipo no están presentes. No fueron diseñados, y son menos que óptimos para, dejando un parámetro de tipo no especificado; para eso están los tipos de comodines, es decirsi el código para un nivel de cumplimiento del compilador superior a 1,5 usted debe escribir

Test<?> test = makeTest(); 
    test.test(animalList); 
    test.test(mammalList); 
    test.test(dogList); 

y regocijarse (o maldición, ya que el asunto puede ser) en granjas los errores de compilación de nuevo.

+0

+1. Debo agregar, que aunque se compila en Eclipse, es seguro que produce un montón de advertencias aterradoras, a las que debes prestar atención. –

+0

En realidad, no solo eclipse, esa advertencia es obligatoria según la especificación: "Una invocación de un método o constructor de un tipo sin procesar genera una advertencia no verificada si la borradura cambia cualquiera de los tipos de cualquiera de los argumentos al método o al constructor". En general, casi todos los códigos que pueden causar * contaminación acumulativa * en tiempo de ejecución son necesarios para generar una advertencia no comprobada en tiempo de compilación. – meriton

1

pregunta interesante.

de que confirmara su resultado mediante la compilación por mí mismo y de hecho lo hace compilar (con advertencias) si se agrega el parámetro de tipo no utilizado. Sin embargo, falla al compilar de nuevo si en realidad se especifica un tipo para el parámetro de tipo:

new Test<Object>().test(animalList); 
    new Test<Object>().test(mammalList); 
    new Test<Object>().test(dogList); 

Mi sospecha es que, debido a que está utilizando una operación sin marcar para construir el objeto de prueba, el compilador no se molesta en verifique los otros tipos de parámetros y trate todo como no seleccionado/inseguro. Cuando especifica el tipo, vuelve a su comportamiento anterior.

0

Si agrega <T> a su clase de prueba y luego rellenar el genérico con algo que el error de compilación regresará

new Test<String>().test(mammalList); 

Mi conjetura es que debido a que la prueba genérica no se ha definido, el compilador decide que no tiene suficiente información para verificar cualquier cosa más allá de ese nivel.

1

Se han parametrizado el tipo añadiendo:

public class Test<T> { 

Pero luego usarlo como el tipo de prima haciendo:

new Test() 

Así que todas las apuestas están apagadas ahora. Para habilitar la interoperabilidad con el código heredado, el compilador lo deja pasar, pero ahora no se trata de comprobar el tipo. Sin embargo, generará una advertencia de compilación.

Cuestiones relacionadas