Estos dos ejemplos son equivalentes y, de hecho, se compilarán con el mismo código de bytes.
Hay dos formas en que agregar un tipo genérico acotado a un método como en su primer ejemplo hará cualquier cosa.
Pasar el parámetro de tipo a otro tipo
Estas dos firmas de métodos terminan siendo las mismas en el código de bytes, pero el compilador impone la seguridad de tipo:
public static <T extends Animal> void addAnimals(Collection<T> animals)
public static void addAnimals(Collection<Animal> animals)
En el primer caso, solo se permite un Collection
(o subtipo) de Animal
. En el segundo caso, se permite un Collection
(o subtipo) con un tipo genérico Animal
o un subtipo.
Por ejemplo, lo siguiente está permitido en el primer método pero no en el segundo:
List<Cat> cats = new ArrayList<Cat>();
cats.add(new Cat());
addAnimals(cats);
La razón es que el segundo solo permite colecciones de animales, mientras que el primero permite colecciones de cualquier objeto que se pueda asignar a un animal (es decir, subtipos). Tenga en cuenta que si esta lista fuera una lista de animales que por casualidad contienen un gato, cualquiera de los métodos lo aceptaría: el problema es la especificación genérica de la colección, no lo que realmente contiene.
Devolviendo objetos
La otra vez que importa es con la devolución de objetos. Supongamos que existiera el siguiente método:
public static <T extends Animal> T feed(T animal) {
animal.eat();
return animal;
}
Podrías hacer lo siguiente con él:
Cat c1 = new Cat();
Cat c2 = feed(c1);
Si bien este es un ejemplo artificial, hay casos en los que tiene sentido. Sin genéricos, el método tendría que regresar Animal
y necesitaría agregar conversión de tipos para que funcione (que es lo que el compilador agrega al código de bytes de todos modos detrás de escena).
addAnimals(List<Animal>)
y agregar una Lista de gatos!