Comme c'était le cas avec Item28, le paramètre type est variant. Ainsi, par exemple,
List n'est pas un sous-type de
List <Object> ''
. Tout ce que List <Object> '' peut faire ne peut pas être fait avec
List
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
Pensez à ajouter une méthode qui prend une séquence d'éléments comme argument et les met tous sur la pile. La première chose à laquelle il faut penser est:
// pushAll method without wildcard type - deficient!
public void pushAll(Iterable<E> src) {
for (E e : src)
push(e);
}
Cette méthode peut être compilée, mais elle n'est pas satisfaisante. Si l'élément de Iterable <E> src
correspond à l'élément de Stack, cela fonctionne correctement, mais par exemple, le code suivant entraînera une erreur. La raison de l'erreur est que le paramètre de type est invariant.
Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);
Pour traiter l'erreur qui apparaît ici, utilisez des caractères génériques pour les arguments de type.
// Wildcard type for a parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
Cette prise en charge le rend compilable et sécurisé.
Ensuite, considérez la méthode `` popAll '', qui prend la collection comme argument et lui transfère tous les éléments stockés dans la pile. Le projet est le suivant.
// popAll method without wildcard type - deficient!
public void popAll(Collection<E> dst) {
while (!isEmpty())
dst.add(pop());
}
Cela fonctionne bien si l'élément E correspond exactement à celui de Stack, mais pas si ce n'est pas le cas. Autrement dit, le code suivant entraînera une erreur de compilation.
Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ... ;
numberStack.popAll(objects);
Pour gérer cela, utilisez également des caractères génériques pour les arguments de type, comme indiqué ci-dessous.
// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
La leçon ici est claire: ** utilisez des caractères génériques lorsque l'argument d'entrée agit en tant que producteur ou consommateur **. Si l'argument d'entrée sert à la fois de producteur et de consommateur, il n'est pas nécessaire d'utiliser des caractères génériques. L'utilisation est mémorisée comme suit.
PECS stands for producer-extends, consumer-super.
Cela signifie que si la variable de type T est un producteur, alors `<? Etend T>`
est utilisé, et s'il s'agit d'un consommateur, alors
<? Super T>
`` est utilisé. Représente. L'exemple Stack ci-dessus est comme ça.
Un exemple concret de ce PECS sera appliqué aux exemples de ce chapitre. Considérez le constructeur suivant pour Item28.
public Chooser(Collection<T> choices)
Puisque cet argument joue le rôle de producteur, il devient comme suit selon PECS.
// Wildcard type for parameter that serves as an T producer
public Chooser(Collection<? extends T> choices)
Ensuite, considérez la méthode d'union suivante de Item30.
public static <E> Set<E> union(Set<E> s1, Set<E> s2)
Les deux arguments jouent le rôle de producteur, ils sont donc les suivants.
public static <E> Set<E> union(Set<? extends E> s1,
Set<? extends E> s2)
La mise en garde ici est que ** vous ne devez pas utiliser de caractères génériques de limite pour le type de retour **. Une telle réponse forcerait les utilisateurs à utiliser des caractères génériques dans leur code, plutôt que d'augmenter la flexibilité. ** Si l'utilisateur doit penser aux caractères génériques de limite lors de l'utilisation d'une classe, il peut y avoir un problème avec son API. ** **
Avant Java8, vous devez spécifier le type comme suit.
// Explicit type parameter - required prior to Java 8
Set<Number> numbers = Union.<Number>union(integers, doubles);
Ensuite, considérez la méthode max de Item30.
public static <T extends Comparable<T>> T max(List<T> list)
Lorsque cela est appliqué à PECS, cela devient comme suit.
public static <T extends Comparable<? super T>> T max(
List<? extends T> list)
Ici, PECS est appliqué deux fois.
** Comparable est toujours un consommateur, vous devez donc choisir Comparable <? Super T>
dans
Comparable **. ** Le comparateur est le même, vous devez donc sélectionner
Comparateur <? Super T> '' dans Comparateur <T>
`` **.
Un autre problème à discuter concernant les caractères génériques est de savoir s'il faut utiliser des paramètres de type ou des caractères génériques. Par exemple, il existe deux méthodes possibles pour permuter les listes: en utilisant des paramètres de type et en utilisant des caractères génériques.
// Two possible declarations for the swap method
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);
Quant à celui qui est préféré, la méthode utilisant le deuxième caractère générique est préférée car elle est plus simple. En général, si une variable de type ** n'apparaît qu'à un seul endroit d'une méthode, elle doit être remplacée par un caractère générique. ** ** La deuxième méthode entraînera une erreur de compilation si vous n'écrivez que cela, comme indiqué ci-dessous.
public static void swap(List<?> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
Liste des types<capture#2-of ?>Ensemble de méthodes(int, capture#2-of ?)Est l'argument(int, capture#3-of ?)Non applicable à
Pour résoudre ce problème, créez une classe d'assistance qui saisit des caractères génériques.
package tryAny.effectiveJava;
import java.util.List;
public class GenericesTest9 {
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
// Private helper method for wildcard capture
private static <E> void swapHelper(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
}
Cela rend la mise en œuvre un peu plus compliquée, mais permet à l'utilisateur de voir une méthode d'échange simple.
Recommended Posts