Je rencontre le code Java comme ceci:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
Quelle est la différence entre les trois précédents et comment appelle-t-on ce type de déclaration de classe ou d'interface en Java?
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 (E
ou T
).
La troisième n'est pas une déclaration valide - ?
est utilisé comme caractère générique utilisé pour fournir un type argument , par exemple List<?> foo = ...
signifie que foo
fait référence à une liste d'un type quelconque, mais nous ne savons pas quoi.
Tout cela est générique , qui est un sujet assez vaste. Vous voudrez peut-être en savoir plus à travers les ressources suivantes, bien qu’il y en ait d’autres disponibles bien sûr:
C'est plus conventionnel qu'autre chose.
T
est censé être un typeE
est censé être un élément (List<E>
: une liste d'éléments)K
est la clé (dans un Map<K,V>
)V
est Value (en tant que valeur de retour ou valeur mappée)Ils sont totalement interchangeables (nonobstant des conflits dans la même déclaration).
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 "?" Ni les différences entre eux, je vais donc répondre à cela.
Tout d’abord, pour que les choses soient bien claires: les paramètres de joker 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 limitation (extends
ou super
), le caractère générique signifie "utiliser n'importe quel type ici".
Le caractère générique est 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éthodes. Les éléments suivants sont identiques sur le plan fonctionnel:
public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething) {...}
Alors, s'il y a un 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 de un type param, vous devez utiliser un caractère générique juste pour simplifier le code./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 l'étendue; le joker n'est pas. Sinon, il existe deux grandes différences entre le type params et le caractère générique:
Les paramètres de type peuvent avoir plusieurs classes englobantes; le joker 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 Integer
comme une limite inférieure du caractère générique, ce qui signifie que le type List doit être Integer ou un super-type Integer. La délimitation de type générique va au-delà de ce que je veux couvrir en détail. En bref, cela 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
. Cela ne fonctionnera pas sans la délimitation du type - c'est exactement ce 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);
}
numberSuper
peut être une liste de nombres ou n'importe quel sur-type de nombre (par exemple, List<Object>
) et elem
doit être un nombre ou un sous-type. Avec toutes les limites, le compilateur peut être certain que la .add()
est dactylographiée.
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ètre de type les plus couramment utilisés sont:
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
Les noms de paramètre 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
le compilateur crée un capture pour chaque caractère générique (par exemple, un point d'interrogation dans la liste) lorsqu'il constitue 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 ok et en ferait une méthode générique :
<V>void foo(List<V> list) {
list.put(list.get())
}