[JAVA] Élément 31: Utilisez des caractères génériques délimités pour augmenter la flexibilité de l'API

31. Des caractères génériques de limite doivent être utilisés pour la flexibilité de l'API

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 '' '', il est donc également d'accord avec le principe de substitution de Riskoff. (Item10) Parfois, plus de flexibilité est nécessaire. Considérez la classe Stack utilisée dans Item29 comme exemple. L'API de la classe Stack est la suivante.

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

Élément 31: Utilisez des caractères génériques délimités pour augmenter la flexibilité de l'API
Comment utiliser l'API Chain
Point 41: Utiliser les interfaces de marqueurs pour définir les types
Comment utiliser l'API Java avec des expressions lambda
Résumé de l'API de communication Java (3) Comment utiliser SocketChannel
Résumé de l'API de communication Java (2) Comment utiliser HttpUrlConnection