Je lis sur les méthodes génériques de OracleDocGenericMethod . Je suis assez confus à propos de la comparaison quand il est dit quand utiliser un joker et quand utiliser des méthodes génériques. Citant du document.
interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); }
Nous aurions pu utiliser des méthodes génériques ici:
interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); // Hey, type variables can have bounds too! }
[…] Cela nous indique que l'argument type est utilisé pour le polymorphisme; Son seul effet est de permettre l'utilisation d'une variété de types d'arguments réels sur différents sites d'invocation. Si tel est le cas, il convient d'utiliser des caractères génériques. Les caractères génériques sont conçus pour prendre en charge les sous-types flexibles, ce que nous essayons d'exprimer ici.
Ne pensons-nous pas que les cartes de joker sont comme (Collection<? extends E> c);
supporte-t-il aussi une sorte de polymorphisme? Alors, pourquoi l’utilisation de méthodes génériques n’est-elle pas considérée comme bonne?
Continuant devant, il dit,
Les méthodes génériques permettent d'utiliser des paramètres de type pour exprimer des dépendances entre les types d'un ou plusieurs arguments d'une méthode et/ou de son type de retour. S'il n'y a pas une telle dépendance, une méthode générique ne doit pas être utilisée.
Qu'est-ce que ça veut dire?
Ils ont présenté l'exemple
class Collections { public static <T> void copy(List<T> dest, List<? extends T> src) { ... }
[…]
Nous aurions pu écrire la signature de cette méthode d'une autre manière, sans utiliser de caractère générique:
class Collections { public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }
Le document décourage la seconde déclaration et encourage l'utilisation de la première syntaxe? Quelle est la différence entre la première et la deuxième déclaration? Les deux semblent faire la même chose?
Quelqu'un peut-il éclairer cette zone?.
Il existe certains endroits, où les caractères génériques et les paramètres de type font la même chose. Mais il existe également certains endroits où vous devez utiliser des paramètres de type.
En prenant votre méthode comme exemple, supposons que vous vouliez vous assurer que la liste src
et dest
transmise à la méthode copy()
soit du même type paramétré, vous pouvez le faire avec le type paramètres tels que:
public static <T extends Number> void copy(List<T> dest, List<T> src)
Ici, vous êtes assuré que dest
et src
ont le même type paramétré pour List
. Donc, il est prudent de copier des éléments de src
vers dest
.
Mais si vous continuez à changer de méthode pour utiliser un caractère générique:
public static void copy(List<? extends Number> dest, List<? extends Number> src)
cela ne fonctionnera pas comme prévu. Dans le second cas, vous pouvez passer List<Integer>
et List<Float>
comme dest
et src
. Ainsi, déplacer des éléments de src
à dest
ne serait plus du type sûr. Si vous n'avez pas besoin de ce type de relation, vous êtes libre de ne pas utiliser de paramètres de type.
Une autre différence entre l'utilisation de caractères génériques et les paramètres de type est la suivante:
Les caractères génériques prennent en charge les limites supérieure et inférieure, les paramètres de type ne prennent en charge que les limites supérieures. Donc, si vous voulez définir une méthode qui prend un List
de type Integer
ou sa super classe, vous pouvez faire:
public void print(List<? super Integer> list) // OK
mais vous ne pouvez pas utiliser le paramètre type:
public <T super Integer> void print(List<T> list) // Won't compile
Références:
Dans votre première question: Cela signifie que s'il existe une relation entre le type du paramètre et le type de retour de la méthode, utilisez un nom générique.
Par exemple:
public <T> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);
Ici, vous extrayez une partie du T en fonction de certains critères. Si T est Long
vos méthodes renverront Long
et Collection<Long>
; Le type de retour réel dépend du type de paramètre. Il est donc utile et conseillé d'utiliser des types génériques.
Si ce n'est pas le cas, vous pouvez utiliser des types de caractères génériques:
public int count(Collection<?> items);
public boolean containsDuplicate(Collection<?> items);
Dans ces deux exemples, quel que soit le type des éléments des collections, les types de retour seront int
et boolean
.
Dans vos exemples:
interface Collection<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
ces deux fonctions renverront un booléen quel que soit le type des éléments des collections. Dans le second cas, il est limité aux instances d'une sous-classe de E.
Deuxième question:
class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src) {
...
}
Ce premier code vous permet de passer un List<? extends T> src
en tant que paramètre. Cette liste peut contenir plusieurs éléments de différentes classes à condition qu’ils élargissent tous la classe de base T.
si tu avais:
interface Fruit{}
et
class Apple implements Fruit{}
class Pear implements Fruit{}
class Tomato implements Fruit{}
vous pourriez faire
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
basket.add(new Apple());
basket.add(new Pear());
basket.add(new Tomato());
List<Fruit> fridge = new ArrayList<Fruit>();
Collections.copy(fridge, basket);// works
D'autre part
class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src) {
...
}
contraindre List<S> src
appartenir à une classe particulière S qui est une sous-classe de T. La liste ne peut contenir que des éléments d’une classe (ici S) et aucune autre classe, même s’ils implémentent également T. Vous ne pourriez pas utiliser mon exemple précédent mais vous pourriez faire:
List<Apple> basket = new ArrayList<Apple>();
basket.add(new Apple());
basket.add(new Apple());
basket.add(new Apple());
List<Fruit> fridge = new ArrayList<Fruit>();
Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */
Vous pouvez suivre l'exemple de la Java Programmation par James Gosling, 4ème édition ci-dessous, où nous souhaitons fusionner 2 SinglyLinkQueue:
public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
// merge s element into d
}
public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
// merge s element into d
}
Les deux méthodes ci-dessus ont les mêmes fonctionnalités. Alors, lequel est préférable? La réponse est 2e. Dans les propres mots de l'auteur:
"La règle générale est d'utiliser des caractères génériques quand vous le pouvez car le code avec des caractères génériques est généralement plus lisible que le code avec plusieurs paramètres de type. Lorsque vous décidez si vous avez besoin d'une variable de type, demandez-vous si cette variable de type est utilisée pour relier deux ou plusieurs paramètres, ou pour associer un type de paramètre au type de retour. Si la réponse est non, un caractère générique devrait suffire. "
Remarque: Dans le livre seulement, la deuxième méthode est indiquée et le nom du paramètre de type est S au lieu de 'T'. La première méthode n'est pas là dans le livre.
Je vais essayer de répondre à votre question, un par un.
Ne pensons-nous pas que les cartes de joker sont comme
(Collection<? extends E> c);
supporte-t-il aussi une sorte de polymorphisme?
La raison en est que le caractère générique délimité n'a pas de type de paramètre défini. C'est un inconnu. Tout ce qu'il "sait", c'est que le "confinement" est de type E
(quelle que soit sa définition). Ainsi, il ne peut pas vérifier et justifier si la valeur fournie correspond au type lié.
Il n’est donc pas judicieux d’avoir des comportements polymorphes sur des caractères génériques.
Le document décourage la seconde déclaration et encourage l'utilisation de la première syntaxe? Quelle est la différence entre la première et la deuxième déclaration? Les deux semblent faire la même chose?
La première option est meilleure dans ce cas car T
est toujours lié, et source
aura certainement des valeurs (inconnues) que les sous-classes T
.
Donc, supposons que vous vouliez copier toute la liste de nombres, la première option sera
Collections.copy(List<Number> dest, List<? extends Number> src);
src
peut accepter essentiellement List<Double>
, List<Float>
, etc., car il existe une limite supérieure au type paramétrable qui se trouve dans dest
.
La 2ème option vous obligera à lier S
pour chaque type que vous voulez copier, comme si
//For double
Collections.copy(List<Number> dest, List<Double> src); //Double extends Number.
//For int
Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.
As S
est un type paramétré qui nécessite une liaison.
J'espère que ça aide.
Une autre différence qui n'est pas listée ici.
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // correct
}
}
Mais ce qui suit entraînera une erreur de temps de compilation.
static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
for (T o : a) {
c.add(o); // compile time error
}
}
La méthode générique est également générique - vous pouvez l'appeler avec une gamme de types.
Le <T>
La syntaxe définit un nom de variable de type. Si une variable de type a une utilité quelconque (par exemple, dans l’implémentation de méthode ou en tant que contrainte pour un autre type), il est logique de la nommer, sinon vous pouvez utiliser ?
, en tant que variable anonyme. Donc, ça ressemble à un raccourci.
De plus, le ?
_ la syntaxe n’est pas évitable lorsque vous déclarez un champ:
class NumberContainer
{
Set<? extends Number> numbers;
}
Pour autant que je sache, il n’existe qu’un seul cas d’utilisation pour lequel un caractère générique est strictement nécessaire (c’est-à-dire que vous pouvez exprimer quelque chose que vous ne pouvez pas exprimer à l’aide de paramètres de type explicite). C'est à ce moment que vous devez spécifier une limite inférieure.
En dehors de cela, les caractères génériques servent à écrire un code plus concis, comme décrit dans les déclarations suivantes du document que vous mentionnez:
Les méthodes génériques permettent d'utiliser des paramètres de type pour exprimer des dépendances entre les types d'un ou plusieurs arguments d'une méthode et/ou de son type de retour. S'il n'y a pas une telle dépendance, une méthode générique ne doit pas être utilisée.
[...]
L'utilisation de caractères génériques est plus claire et concise que la déclaration de paramètres de type explicites et doit donc être privilégiée dans la mesure du possible.
[...]
Les caractères génériques ont également l'avantage de pouvoir être utilisés en dehors des signatures de méthodes, comme les types de champs, les variables locales et les tableaux.