¿Cuál es la diferencia entre 'E', 'T' y '?' para los genéricos de Java?

261

Me encuentro con un código Java como este:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

¿Cuál es la diferencia entre los tres anteriores y cómo llaman a este tipo de declaraciones de clase o interfaz en Java?

as
fuente
1
Dudo que haya alguna diferencia. Supongo que es solo un nombre para ese tipo de parámetro. ¿Y el último es válido?
CodesInChaos

Respuestas:

231

Bueno, no hay diferencia entre los dos primeros: solo usan nombres diferentes para el parámetro de tipo ( Eo T).

La tercera no es una declaración válida: ?se usa como comodín que se usa cuando se proporciona un argumento de tipo , por ejemplo, List<?> foo = ...significa que se foorefiere a una lista de algún tipo, pero no sabemos qué.

Todo esto es genérico , que es un tema bastante importante. Es posible que desee conocerlo a través de los siguientes recursos, aunque, por supuesto, hay más disponibles:

Jon Skeet
fuente
1
Parece que el enlace al PDF está roto. He encontrado lo que parece ser una copia aquí , pero no puedo estar 100% seguro ya que no sé cómo era el original.
John
2
@ John: Sí, ese es el. Editará un enlace, ya sea uno u Oracle ...
Jon Skeet
¿Hay algo más que T, E y? utilizado en genéricos? Si es así, ¿qué son y qué significan?
sofs1
1
@ sofs1: No tiene nada de especial Ty Eson solo identificadores. Podrías escribir KeyValuePair<K, V>por ejemplo. ?Sin embargo, tiene un significado especial.
Jon Skeet
215

Es más convención que cualquier otra cosa.

  • T está destinado a ser un tipo
  • Eestá destinado a ser un Elemento ( List<E>: una lista de Elementos)
  • Kes clave (en a Map<K,V>)
  • V es Valor (como valor de retorno o valor asignado)

Son totalmente intercambiables (a pesar de los conflictos en la misma declaración).

monstruo de trinquete
fuente
20
La letra entre <> es solo un nombre. Lo que describe en su respuesta son solo convenciones. Ni siquiera tiene que ser una letra mayúscula; puede usar cualquier nombre que desee, al igual que puede dar clases, variables, etc., cualquier nombre que desee.
Jesper
Una descripción más detallada y clara está disponible en este artículo oracle.com/technetwork/articles/java/…
fgul
66
No le explicaste al signo de interrogación. Voto negativo
Shinzou
129

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 ( extendso 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 Integercomo 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);
}

numberSuperpuede ser una Lista de números o cualquier supertipo de Número (por ejemplo, List<Object>), y elemdebe ser Número o cualquier subtipo. Con todos los límites, el compilador puede estar seguro de que .add()es seguro.

Hawkeye Parker
fuente
"public void foo (Lista <? extiende Número> números) {...}" debería "extiende" ser "super"?
1a1a11a
1
No. El objetivo de ese ejemplo es mostrar una firma que admite polimórficamente una Lista de números y los subtipos de Número. Para esto, usas "extiende". Es decir, "pásame una lista de números o cualquier cosa que extienda el número" (List <Integer>, List <Float>, lo que sea). Un método como este podría iterar a través de la lista y, para cada elemento, "e", ejecutar, por ejemplo, e.floatValue (). No importa qué subtipo (extensión) de Número pase; siempre podrá ".floatValue ()", porque .floatValue () es un método de Número.
Hawkeye Parker
En su último ejemplo, "List <? Super Number>" podría ser simplemente "List <Number>" ya que el método no permite nada más genérico.
jessarah
@jessarah no. Tal vez mi ejemplo no está claro, pero menciono en el ejemplo que adder () podría tomar una Lista <Objeto> (El objeto es una superclase de Número). Si desea que pueda hacer esto, debe tener la firma "Lista <? Super número>". Ese es exactamente el punto de "super" aquí.
Hawkeye Parker
2
Esta respuesta es muy buena para explicar las diferencias entre los comodines y los parámetros de tipo, debería haber una pregunta específica con esta respuesta. Estoy profundizando en los genéricos últimamente y esta respuesta me ayudó mucho a armar las cosas, muchas informaciones precisas en resumen, ¡gracias!
Testo Testini
27

Una variable de tipo, <T>, puede ser cualquier tipo no primitivo que especifique: cualquier tipo de clase, cualquier tipo de interfaz, cualquier tipo de matriz o incluso otra variable de tipo.

Los nombres de parámetros de tipo más utilizados son:

  • E - Elemento (usado ampliamente por el Java Collections Framework)
  • K - Clave
  • N - Número
  • T - Tipo
  • V - Valor

En Java 7 se permite crear instancias de esta manera:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
Uva
fuente
3

Los nombres de parámetros de tipo más utilizados son:

E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types

Verá estos nombres utilizados en la API Java SE

Waqas Ahmed
fuente
2

el compilador realizará una captura para cada comodín (p. ej., signo de interrogación en la Lista) cuando cree una función como:

foo(List<?> list) {
    list.put(list.get()) // ERROR: capture and Object are not identical type.
}

Sin embargo, un tipo genérico como V estaría bien y lo convertiría en un método genérico :

<V>void foo(List<V> list) {
    list.put(list.get())
}
Tiina
fuente