Las respuestas anteriores explican los parámetros de tipo (T, E, etc.), pero no explican el comodín, "?", O las diferencias entre ellos, así que abordaré eso.
Primero, para ser claros: los parámetros comodín y tipo no son lo mismo. Cuando los parámetros de tipo definen un tipo de variable (por ejemplo, T) que representa el tipo para un ámbito, el comodín no: el comodín simplemente define un conjunto de tipos permitidos que puede usar para un tipo genérico. Sin ningún límite ( extends
o super
), el comodín significa "usar cualquier tipo aquí".
El comodín siempre viene entre paréntesis angulares, y solo tiene significado en el contexto de un tipo genérico:
public void foo(List<?> listOfAnyType) {...} // pass a List of any type
Nunca
public <?> ? bar(? someType) {...} // error. Must use type params here
o
public class MyGeneric ? { // error
public ? getFoo() { ... } // error
...
}
Se vuelve más confuso donde se superponen. Por ejemplo:
List<T> fooList; // A list which will be of type T, when T is chosen.
// Requires T was defined above in this scope
List<?> barList; // A list of some type, decided elsewhere. You can do
// this anywhere, no T required.
Hay mucha superposición en lo que es posible con las definiciones de métodos. Los siguientes son, funcionalmente, idénticos:
public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething) {...}
Entonces, si hay superposición, ¿por qué usar uno u otro? A veces, honestamente es solo estilo: algunas personas dicen que si no necesita un parámetro de tipo, debe usar un comodín solo para hacer que el código sea más simple / legible. Una diferencia principal que expliqué anteriormente: los parámetros de tipo definen una variable de tipo (por ejemplo, T) que puede usar en cualquier otra parte del alcance; el comodín no. De lo contrario, hay dos grandes diferencias entre los parámetros de tipo y el comodín:
Los params de tipo pueden tener múltiples clases limitantes; el comodín no puede:
public class Foo <T extends Comparable<T> & Cloneable> {...}
El comodín puede tener límites inferiores; los params tipo no pueden:
public void bar(List<? super Integer> list) {...}
En lo anterior, se List<? super Integer>
define Integer
como un límite inferior en el comodín, lo que significa que el tipo de Lista debe ser Entero o un supertipo de Entero. El límite de tipo genérico está más allá de lo que quiero cubrir en detalle. En resumen, le permite definir qué tipos puede ser un tipo genérico. Esto hace posible el tratamiento de genéricos polimórficos. Por ejemplo con:
public void foo(List<? extends Number> numbers) {...}
Puede pasar un List<Integer>
, List<Float>
, List<Byte>
, etc., para numbers
. Sin delimitación de tipos, esto no funcionará, así son los genéricos.
Finalmente, aquí hay una definición de método que usa el comodín para hacer algo que no creo que pueda hacer de otra manera:
public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
numberSuper.add(elem);
}
numberSuper
puede ser una Lista de números o cualquier supertipo de Número (por ejemplo, List<Object>
), y elem
debe ser Número o cualquier subtipo. Con todos los límites, el compilador puede estar seguro de que .add()
es seguro.