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 ( extends
ou 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 Integer
comme 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);
}
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 n'importe quel sous-type. Avec toutes les limites, le compilateur peut être certain que le type .add()
est sûr.