[JAVA] Punkt 31: Verwenden Sie begrenzte Platzhalter, um die API-Flexibilität zu erhöhen

31. Für die API-Flexibilität sollten Grenz-Platzhalter verwendet werden

Wie bei Item28 ist der Typparameter eine Variante. So ist beispielsweise `List <String>` kein Subtyp von `List <Object>`. Nicht alles, was "List " kann, kann mit "List " ausgeführt werden, daher stimmt es auch mit dem Substitutionsprinzip von Riskoff überein. (Punkt 10) Manchmal ist mehr Flexibilität erforderlich. Betrachten Sie als Beispiel die in Item29 verwendete Stack-Klasse. Die API der Stack-Klasse lautet wie folgt.

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

Fügen Sie eine Methode hinzu, die eine Folge von Elementen als Argument verwendet und sie alle auf den Stapel legt. Das erste, woran man denken muss, ist:

// pushAll method without wildcard type - deficient!
public void pushAll(Iterable<E> src) {
    for (E e : src)
        push(e);
}

Diese Methode kann kompiliert werden, ist aber nicht zufriedenstellend. Wenn das Element von `` `Iterable src``` mit dem Element von Stack übereinstimmt, funktioniert es einwandfrei, aber der folgende Code führt beispielsweise zu einem Fehler. Der Grund für den Fehler ist, dass der Typparameter unveränderlich ist.

Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);

Verwenden Sie Platzhalter für Typargumente, um den hier angezeigten Fehler zu beheben.

// Wildcard type for a parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
    for (E e : src)
        push(e);
}

Diese Unterstützung macht es kompilierbar und typsicher.

Betrachten Sie als nächstes die `` `popAll``` -Methode, die die Sammlung als Argument verwendet und alle im Stapel gespeicherten Elemente darauf überträgt. Der Entwurf lautet wie folgt.

// popAll method without wildcard type - deficient!
public void popAll(Collection<E> dst) {
    while (!isEmpty())
        dst.add(pop());
}

Dies funktioniert gut, wenn Element E genau mit dem von Stack übereinstimmt, aber nicht, wenn dies nicht der Fall ist. Das heißt, der folgende Code führt zu einem Kompilierungsfehler.

Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ... ;
numberStack.popAll(objects);

Verwenden Sie dazu auch hier Platzhalter für Typargumente, wie unten gezeigt.

// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
    while (!isEmpty())
        dst.add(pop());
}

Die Lektion hier ist klar: ** Verwenden Sie Platzhalter, wenn das Eingabeargument entweder als Produzent oder als Konsument fungiert **. Wenn das Eingabeargument sowohl als Produzent als auch als Konsument dient, müssen keine Platzhalter verwendet werden. Die Verwendung wird wie folgt gespeichert.

PECS stands for producer-extends, consumer-super.

Dies bedeutet, dass, wenn die Typvariable T ein Produzent ist, `<? Erweitert T>` `verwendet wird, und wenn es ein Verbraucher ist, dann` <? Super T> `` `verwendet wird. Repräsentiert. Das obige Stapelbeispiel ist so.

Ein konkretes Beispiel für dieses PECS wird auf die Beispiele in diesem Kapitel angewendet. Betrachten Sie den folgenden Konstruktor für Item28.

public Chooser(Collection<T> choices)

Dieses Argument spielt die Rolle des Produzenten, so dass es laut PECS wie folgt lautet.

// Wildcard type for parameter that serves as an T producer
public Chooser(Collection<? extends T> choices)

Betrachten Sie als nächstes die folgende Vereinigungsmethode von Item30.

public static <E> Set<E> union(Set<E> s1, Set<E> s2)

Beide Argumente spielen die Rolle des Produzenten und lauten daher wie folgt.

public static <E> Set<E> union(Set<? extends E> s1,
                               Set<? extends E> s2)

Die Einschränkung hierbei ist, dass ** Sie keine Begrenzungs-Platzhalter für den Rückgabetyp ** verwenden dürfen. Eine solche Antwort würde Benutzer dazu zwingen, Platzhalter in ihrem Code zu verwenden, anstatt die Flexibilität zu erhöhen. ** Wenn der Benutzer bei der Verwendung einer Klasse über Rand-Platzhalter nachdenken muss, liegt möglicherweise ein Problem mit der API vor. ** ** **

Vor Java8 müssen Sie den Typ wie folgt angeben.

// Explicit type parameter - required prior to Java 8
Set<Number> numbers = Union.<Number>union(integers, doubles);

Betrachten Sie als nächstes die Max-Methode von Item30.

public static <T extends Comparable<T>> T max(List<T> list)

Wenn dies auf PECS angewendet wird, wird es wie folgt.

public static <T extends Comparable<? super T>> T max(
        List<? extends T> list)

Hier wird PECS zweimal angewendet. ** Vergleichbar ist immer ein Verbraucher, daher sollten Sie `Vergleichbar <? Super T>` aus `Vergleichbar <T>` `** auswählen. ** Der Komparator ist der gleiche, daher sollten Sie Komparator <? Super T> aus Komparator <T> `` ** auswählen.

Ein weiteres zu diskutierendes Problem in Bezug auf Platzhalter ist die Verwendung von Typparametern oder Platzhaltern. Beispielsweise gibt es zwei mögliche Methoden zum Austauschen von Listen: Verwenden von Typparametern und Verwenden von Platzhaltern.

// 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);

Was das bevorzugte betrifft, wird das Verfahren unter Verwendung des zweiten Platzhalters bevorzugt, weil es einfacher ist. Wenn eine Variable vom Typ ** in einer Methode nur an einer Stelle angezeigt wird, sollte sie im Allgemeinen durch einen Platzhalter ersetzt werden. ** ** ** Die zweite Methode führt zu einem Kompilierungsfehler, wenn Sie nur diesen schreiben, wie unten gezeigt.

public static void swap(List<?> list, int i, int j) {
    list.set(i, list.set(j, list.get(i)));
}
Typ Liste<capture#2-of ?>Methodensatz(int, capture#2-of ?)Ist das Argument(int, capture#3-of ?)Gilt nicht für

Um dies zu lösen, erstellen Sie eine Hilfsklasse, die Platzhalter eingibt.

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)));
    }

}

Dies macht die Implementierung etwas komplizierter, ermöglicht dem Benutzer jedoch die Anzeige einer einfachen Swap-Methode.