Quelle est la différence entre 'E', 'T' et '?' pour les génériques Java?

261

Je rencontre du code Java comme ceci:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

Quelle est la différence entre les trois ci-dessus et comment appellent-ils ce type de déclarations de classe ou d'interface en Java?

as
la source
1
Je doute qu'il y ait une différence. Je suppose que c'est juste un nom pour ce paramètre de type. Et le dernier est-il même valable?
CodesInChaos

Réponses:

231

Eh bien, il n'y a pas de différence entre les deux premiers - ils utilisent simplement des noms différents pour le paramètre de type ( Eou T).

La troisième n'est pas une déclaration valide - ?est utilisée comme un caractère générique qui est utilisé lors de la fourniture d'un argument de type , par exemple List<?> foo = ...signifie qui foofait référence à une liste d'un certain type, mais nous ne savons pas quoi.

Tout cela est générique , ce qui est un sujet assez vaste. Vous voudrez peut-être en apprendre davantage à ce sujet à travers les ressources suivantes, bien qu'il y en ait plus bien sûr bien sûr:

Jon Skeet
la source
1
Il semble que le lien vers le PDF soit rompu. J'ai trouvé ce qui semble être une copie ici , mais je ne peux pas être sûr à 100% car je ne sais pas à quoi ressemblait l'original.
John
2
@John: Yup, c'est celui-là. Éditera un lien, que ce soit celui d'Oracle ...
Jon Skeet
Y a-t-il autre chose que T, E et? utilisé dans les génériques? Si oui, que sont-ils et que signifient-ils?
sofs1
1
@ sofs1: Il n'y a rien de spécial Tet E- ce ne sont que des identifiants. Vous pourriez écrire KeyValuePair<K, V>par exemple. ?a cependant une signification particulière.
Jon Skeet
215

C'est plus conventionnel qu'autre chose.

  • T est censé être un type
  • Eest censé être un élément ( List<E>: une liste d'éléments)
  • Kest la clé (en a Map<K,V>)
  • V est Value (en tant que valeur de retour ou valeur mappée)

Ils sont entièrement interchangeables (nonobstant conflits dans la même déclaration).

monstre à cliquet
la source
20
La lettre entre les <> n'est qu'un nom. Ce que vous décrivez dans votre réponse ne sont que des conventions. Il n'est même pas nécessaire que ce soit une seule lettre majuscule; vous pouvez utiliser n'importe quel nom que vous aimez, tout comme vous pouvez donner des classes, des variables, etc.
Jesper
Une description plus détaillée et plus claire est disponible dans cet article oracle.com/technetwork/articles/java/…
fgul
6
Vous n'avez pas expliqué au point d'interrogation. Voté.
shinzou
129

Les réponses précédentes expliquent les paramètres de type (T, E, etc.), mais n'expliquent pas le caractère générique, "?", Ou les différences entre eux, donc je vais y répondre.

Tout d'abord, pour être clair: les paramètres génériques et de type ne sont pas les mêmes. Lorsque les paramètres de type définissent une sorte de variable (par exemple, T) qui représente le type d'une étendue, le caractère générique ne le fait pas: le caractère générique définit simplement un ensemble de types autorisés que vous pouvez utiliser pour un type générique. Sans aucune limite ( extendsou super), le caractère générique signifie "utilisez n'importe quel type ici".

Le caractère générique vient toujours entre crochets et n'a de sens que dans le contexte d'un type générique:

public void foo(List<?> listOfAnyType) {...}  // pass a List of any type

jamais

public <?> ? bar(? someType) {...}  // error. Must use type params here

ou

public class MyGeneric ? {      // error
    public ? getFoo() { ... }   // error
    ...
}

Cela devient plus confus là où ils se chevauchent. Par exemple:

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.

Il y a beaucoup de chevauchement dans ce qui est possible avec les définitions de méthode. Les éléments suivants sont, fonctionnellement, identiques:

public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething)  {...}

Donc, s'il y a chevauchement, pourquoi utiliser l'un ou l'autre? Parfois, c'est honnêtement juste du style: certaines personnes disent que si vous n'avez pas besoin d' un paramètre de type, vous devez utiliser un caractère générique juste pour rendre le code plus simple / plus lisible. Une différence principale que j'ai expliquée ci-dessus: les paramètres de type définissent une variable de type (par exemple, T) que vous pouvez utiliser ailleurs dans la portée; pas le caractère générique. Sinon, il existe deux grandes différences entre les paramètres de type et le caractère générique:

Les paramètres de type peuvent avoir plusieurs classes de délimitation; le caractère générique ne peut pas:

public class Foo <T extends Comparable<T> & Cloneable> {...}

Le caractère générique peut avoir des limites inférieures; les paramètres de type ne peuvent pas:

public void bar(List<? super Integer> list) {...}

Dans ce qui précède, le List<? super Integer>définit Integercomme une borne inférieure sur le caractère générique, ce qui signifie que le type de liste doit être Integer ou un super-type Integer. La délimitation de type générique est au-delà de ce que je veux couvrir en détail. En bref, il vous permet de définir quels types un type générique peut être. Cela permet de traiter les génériques de manière polymorphe. Par exemple avec:

public void foo(List<? extends Number> numbers) {...}

Vous pouvez passer un List<Integer>, List<Float>, List<Byte>, etc. pour numbers. Sans délimitation de type, cela ne fonctionnera pas - c'est ainsi que sont les génériques.

Enfin, voici une définition de méthode qui utilise le caractère générique pour faire quelque chose que je ne pense pas que vous puissiez faire autrement:

public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
    numberSuper.add(elem);
}

numberSuperpeut être une liste de nombres ou n'importe quel sur-type de nombre (par exemple, List<Object>), et elemdoit être un nombre ou n'importe quel sous-type. Avec toutes les limites, le compilateur peut être certain que le type .add()est sûr.

Hawkeye Parker
la source
"public void foo (List <? extend Number> nombres) {...}" devrait "extend" être "super"?
1a1a11a
1
Non. Le but de cet exemple est de montrer une signature qui supporte polymorphiquement une liste de nombres et les sous-types de nombres. Pour cela, vous utilisez "étend". C'est-à-dire, "passez-moi une liste de nombre ou tout ce qui étend le nombre" (liste <entier>, <flottant>, peu importe). Une méthode comme celle-ci pourrait alors parcourir la liste et, pour chaque élément, "e", exécuter, par exemple, e.floatValue (). Peu importe le sous-type (extension) de Number que vous passez - vous pourrez toujours ".floatValue ()", car .floatValue () est une méthode de Number.
Hawkeye Parker
Sur votre dernier exemple, "List <? Super Number>" pourrait simplement être "List <Number>" car la méthode ne permet rien de plus générique.
jessarah
@ jessarah non. Peut-être que mon exemple n'est pas clair, mais je mentionne dans l'exemple que adder () pourrait prendre un List <Object> (Object est une superclasse de Number). Si vous voulez qu'il puisse le faire, il doit avoir la signature "List <? Super Number>". C'est exactement le point de "super" ici.
Hawkeye Parker
2
Cette réponse est très très bonne pour expliquer les différences entre les caractères génériques et les paramètres de type, il devrait y avoir une question dédiée avec cette réponse. Je vais plus loin dans les génériques récemment et cette réponse m'a beaucoup aidé à mettre les choses ensemble, beaucoup d'informations précises en bref, merci!
Testo Testini
27

Une variable de type, <T>, peut être tout type non primitif que vous spécifiez: tout type de classe, tout type d'interface, tout type de tableau ou même une autre variable de type.

Les noms de paramètres de type les plus couramment utilisés sont:

  • E - Element (largement utilisé par Java Collections Framework)
  • K - Clé
  • N - Nombre
  • T - Type
  • V - Valeur

Dans Java 7, il est permis d'instancier comme ceci:

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

Les noms de paramètres de type les plus couramment utilisés sont:

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

Vous verrez ces noms utilisés dans l'API Java SE

Waqas Ahmed
la source
2

le compilateur fera une capture pour chaque caractère générique (par exemple, un point d'interrogation dans la liste) quand il crée une fonction comme:

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

Cependant, un type générique comme V serait correct et en ferait une méthode générique :

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