2012-05-24 12 views
7

¿Quién podría explicarme esto?Genéricos de Java: agregar el tipo incorrecto en la colección

tengo este par de clases:

abstract class Animal { 
    public void eat() { 
     System.out.println("Animal is eating"); 
    } 
} 

class Dog extends Animal { 
    public void woof() { 
     System.out.println("woof"); 
    } 
} 

class Cat extends Animal { 
    public void meow() { 
     System.out.println("meow"); 
    } 
} 

y esta es la acción:

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

public class TestClass { 

    public static void main(String[] args) { 
     new TestClass().go(); 
    } 

    public void go() { 
     List<Dog> animals = new ArrayList<Dog>(); 
     animals.add(new Dog()); 
     animals.add(new Dog()); 
     doAction(animals); 
    } 

    public <T extends Animal> void doAction(List<T> animals) { 

     animals.add((T) new Cat()); // why is it possible? 
            // Variable **animals** is List<Dog>, 
            // it is wrong, that I can add a Cat! 

     for (Animal animal: animals) { 
      if (animal instanceof Cat) { 
       ((Cat)animal).meow(); 
      } 
      if (animal instanceof Dog) { 
       ((Dog)animal).woof(); 
      } 
     } 
    } 
} 

En este ejemplo se compilará sin errores, y la salida es:

woof 
woof 
meow 

Pero, ¿cómo ¿Puedo agregar una lista de Dog a Cat? ¿Y cómo le lanzan el gato a Dog?

Yo uso: versión de java "1.6.0_24". OpenJDK Runtime Environment (IcedTea6 1.11.1) (6b24-1.11.1-4ubuntu3)

+1

Hay advertencias de compilación, ¿no? –

+1

Sin duda habrá una advertencia del compilador en animals.add ((T) new Cat()) ;. – tjg184

+1

Y así comienza el rastro de lágrimas que son los genéricos de Java. –

Respuesta

6

autorización aquí es el trato con los genéricos (cualquier cosa que utiliza el casting hackery podría no ser seguro en tiempo de ejecución, porque los genéricos obra de erasure):

Puede asignar un subtipo parametrizar la misma manera, por ejemplo

List<Animal> l = new ArrayList<Animal>(); 

y se puede añadir elementos que son el tipo de este parámetro o sus subclases, por ejemplo

l.add(new Cat()); 
l.add(new Dog()); 

pero sólo se puede salir de la ty PE del parámetro:

Animal a = l.get(0); 
Cat c = l.get(0); //disallowed 
Dog d = l.get(1); //disallowed 

Ahora, se puede utilizar un comodín para establecer un límite superior en el tipo de parámetro

List<? extends Animal> l = new ArrayList<Animal>(); 
List<? extends Animal> l = new ArrayList<Cat>(); 
List<? extends Animal> l = new ArrayList<Dog>(); 

Pero no es posible añadir nuevos elementos a esta lista

l.add(new Cat()); // disallowed 
l.add(new Dog()); // disallowed 

En su caso tiene un List<T> por lo que tiene un método add(T t) para que pueda agregarlo si lo transfiere al T. Pero T tiene el tipo delimitado anteriormente por Animal, por lo que no debería tratar de agregar a esta lista, pero se trata como un tipo concreto y es por eso que permite el reparto. Sin embargo, esto puede arrojar un ClassCastException.

Y sólo se puede recuperar los elementos que son el tipo de límite superior

Animal a = l.get(0); 
Cat c = l.get(0); //disallowed 
Dog d = l.get(1); //disallowed 

o puede establecer el límite inferior tipo de parámetro

List<? super Animal> l1 = new ArrayList<Object>(); 
List<? super Animal> l1 = new ArrayList<Animal>(); 
List<? super Cat> l2 = new ArrayList<Animal>(); 
List<? super Cat> l2 = new ArrayList<Cat>(); 
List<? super Dog> l3 = new ArrayList<Animal>(); 
List<? super Dog> l3 = new ArrayList<Dog>(); 

Y se puede añadir objetos que son subtipos de la parte baja tipo vinculado

l1.add(new Cat()); 
l1.add(new Dog()); 
l1.add(new Object()); //disallowed 

Pero todos los objetos recuperados son del tipo Objeto

Object o = l1.get(0); 
Animal a = l1.get(0); //disallowed 
Cat c = l2.get(0); //disallowed 
Dog d = l3.get(0); //disallowed 
+0

+1 por dar esos ejemplos. – dragon66

0

Los genéricos solo funcionan por seguridad de tiempo de compilación. En su caso, ¿cómo puede el compilador saber que algo malo sucederá? Asume tus definiciones de tipo, y procede de eso. Hacer más significaría un trabajo mucho más elaborado para el compilador, pero hay inspecciones estáticas extendidas y otras herramientas que podrían atrapar este preruntiempo.

1

Esto está relacionado con el tipo de borrado. El tipo no se conserva en tiempo de ejecución. Realmente, List se convierte en List of Type Object en tiempo de ejecución. Es por eso que recibe una advertencia del compilador o debería estar en animals.add ((T) new Cat()); En tiempo de compilación, Cat extiende un animal que es de tipo T. Sin embargo, no puede exigir que un perro esté en la lista en ese momento, por lo tanto, la advertencia del compilador.

0

Baste decir que existe un T para el que el reparto puede tener éxito. En cuanto al código compilado, será exactamente el mismo que si escribió animals.add((Animal)new Cat());

2

No espere que los genéricos realicen la verificación del tipo de tiempo de ejecución. Durante la compilación, Java realiza toda la inferencia de tipo, crea una instancia de todos los tipos, ... y luego borra todo el rastro de tipos genéricos del código. En tiempo de ejecución, el tipo es Lista, no Lista < T> o Lista < Perro>.

La pregunta principal, por qué le permite enviar new Cat() al tipo T extends Animal, con solo una advertencia sobre las conversiones sin marcar, es válida. Ciertas características poco sólidas del sistema de tipo hacen que sea necesario legalizar tales modelos dudosos.

Si desea que el compilador para evitar la adición de nada a la lista, usted debe utilizar un comodín:

public void doAction(List< ? extends Animal > animals) { 
    animals.add(new Cat()); // disallowed by compiler 
    animals.add((Animal)new Cat()); // disallowed by compiler 

    for (Animal animal: animals) { 
     if (animal instanceof Cat) { 
      ((Cat)animal).meow(); 
     } 
     if (animal instanceof Dog) { 
      ((Dog)animal).woof(); 
     } 
    } 
} 

P. S. Los downcasts dudosos en el cuerpo del bucle son un ejemplo perfecto de cuán cojo es Java para los tipos de suma disyuntiva (variante).

+0

1. aún puede convertir a 'List ' y agregarle cosas 2.aún puede pasar esta lista al método original 'doAction' desde arriba (parametrizado con' T') que aún puede agregarle – newacct

0

Su función doAction está parametrizada por un tipo T que amplía la clase Animal. Por lo tanto, puede ser un objeto del tipo Dog o Cat.
También debe tener en cuenta la diferencia entre parámetro formal y parámetro efectivo.
El parámetro formal es el que usa al definir su método, que en su caso es List <T> (usándolo como un acceso directo).
El parámetro efectivo es el que "da" a su método cuando lo llama, aquí List<Dog>.
.
Echemos un vistazo a animals.add((T) new Cat()) en doAction(). animals es una lista de elementos del tipo T que es Dog o Cat, por lo que no hay ningún error, ya que ese es el tipo del parámetro formal.
Esa es la razón. Es una de las ventajas de utilizar clases de parametrización.

+1

Java no se da cuenta de que los únicos Animales son Perros y Gatos. De hecho, eso nunca puede ser el caso, porque uno siempre puede agregar otro. Lo que está sucediendo es que el elenco está siendo revisado (por el compilador) independientemente de la inferencia de tipo que se produce cuando se llama con una 'Lista < Dog >'. Aparentemente la regla es que el lanzamiento está permitido (con una advertencia) si pudiera * alguna vez * ser válido. –

+0

Lo que dije estaba basado en el código que dio el póster. Y sí, por supuesto, uno siempre puede agregar otro. Mi punto era que 'Cat' extiende' Animals' así que agregarlo a una lista de elementos que amplía Animals no está mal. –

Cuestiones relacionadas