Kürzlich habe ich diesen Artikel gelesen: http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html
Meine Frage ist, anstatt eine Methode wie diese zu erstellen:
public void drawAll(List<? extends Shape> shapes){
for (Shape s: shapes) {
s.draw(this);
}
}
Ich kann eine Methode wie diese erstellen, und sie funktioniert einwandfrei:
public <T extends Shape> void drawAll(List<T> shapes){
for (Shape s: shapes) {
s.draw(this);
}
}
Welchen Weg soll ich benutzen? Ist ein Platzhalter in diesem Fall nützlich?
java
api
generics
bounded-wildcard
Tony Le
quelle
quelle
Antworten:
Es hängt davon ab , was Sie brauchen zu tun. Sie müssen den Parameter für den begrenzten Typ verwenden, wenn Sie Folgendes tun möchten:
public <T extends Shape> void addIfPretty(List<T> shapes, T shape) { if (shape.isPretty()) { shapes.add(shape); } }
Hier haben wir ein
List<T> shapes
und einT shape
, deshalb können wir sichershapes.add(shape)
. Wenn es deklariert wurdeList<? extends Shape>
, können Sie NICHT sicheradd
darauf zugreifen (weil Sie möglicherweise einList<Square>
und ein habenCircle
).Indem wir einem Parameter mit begrenztem Typ einen Namen geben, haben wir die Möglichkeit, ihn an anderer Stelle in unserer generischen Methode zu verwenden. Diese Informationen sind natürlich nicht immer erforderlich. Wenn Sie also nicht so viel über den Typ wissen müssen (z. B. Ihren
drawAll
) , ist nur ein Platzhalter ausreichend.Auch wenn Sie nicht erneut auf den Parameter für den begrenzten Typ verweisen, ist ein Parameter für den begrenzten Typ erforderlich, wenn Sie mehrere Grenzen haben. Hier ist ein Zitat aus den Java Generics-FAQs von Angelika Langer
Zitate aus Effective Java 2nd Edition, Punkt 28: Verwenden Sie begrenzte Platzhalter, um die API-Flexibilität zu erhöhen :
Unter Anwendung des PECS-Prinzips können wir nun zu unserem
addIfPretty
Beispiel zurückkehren und es flexibler gestalten, indem wir Folgendes schreiben:public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }
Jetzt können wir
addIfPretty
sagen, aCircle
, zu aList<Object>
. Dies ist offensichtlich typsicher, und dennoch war unsere ursprüngliche Erklärung nicht flexibel genug, um dies zuzulassen.Verwandte Fragen
<? super T>
bedeutet und wann es verwendet werden sollte und wie diese Konstruktion mit<T>
und zusammenarbeiten sollte<? extends T>
?Zusammenfassung
quelle
reverse(List<?>)
Beispiel von JLS java.sun.com/docs/books/jls/third_edition/html/…?
als Methodenparameter übergeben,doWork(? type)
da Sie diesen Parameter dann nicht in der Methode verwenden können. Sie müssen den Typparameter verwenden.In Ihrem Beispiel müssen Sie T nicht wirklich verwenden, da Sie diesen Typ nirgendwo anders verwenden.
Aber wenn Sie so etwas gemacht haben:
public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){ T s = shapes.get(0); s.draw(this); return s; }
oder wie Polygenschmierstoffe sagten, wenn Sie den Typparameter in der Liste mit einem anderen Typparameter abgleichen möchten:
public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) { List<T> mergedList = new ArrayList<T>(); mergedList.addAll(shapes1); mergedList.addAll(shapes2); for (Shape s: mergedList) { s.draw(this); } }
Im ersten Beispiel erhalten Sie etwas mehr Typensicherheit, als nur Shape zurückzugeben, da Sie das Ergebnis dann an eine Funktion übergeben können, die möglicherweise ein Kind von Shape übernimmt. Zum Beispiel können Sie eine übergeben
List<Square>
an meine Methode übergeben und dann das resultierende Quadrat an eine Methode übergeben, die nur Quadrate verwendet. Wenn Sie '?' Sie müssten die resultierende Form auf Quadrat umwandeln, was nicht typsicher wäre.Im zweiten Beispiel stellen Sie sicher, dass beide Listen denselben Typparameter haben (was Sie mit '?' Nicht tun können, da jedes '?' Unterschiedlich ist), damit Sie eine Liste erstellen können, die alle Elemente aus beiden enthält .
quelle
Shape s = ...
sollte seinT s = ...
, sonstreturn s;
sollte nicht kompilieren.Betrachten Sie das folgende Beispiel aus der 4. Ausgabe von The Java Programming von James Gosling, in dem wir 2 SinglyLinkQueue zusammenführen möchten:
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 }
Beide oben genannten Methoden haben die gleiche Funktionalität. Was ist also vorzuziehen? Die Antwort ist die 2.. In den eigenen Worten des Autors:
"Die allgemeine Regel lautet, wenn möglich Platzhalter zu verwenden, da Code mit Platzhaltern im Allgemeinen besser lesbar ist als Code mit mehreren Typparametern. Wenn Sie entscheiden, ob Sie eine Typvariable benötigen, fragen Sie sich, ob diese Typvariable verwendet wird, um zwei oder mehr Parameter zu verknüpfen. oder um einen Parametertyp mit dem Rückgabetyp zu verknüpfen. Wenn die Antwort Nein lautet, sollte ein Platzhalter ausreichen. "
Hinweis: Im Buch wird nur die zweite Methode angegeben, und der Name des Typparameters lautet S anstelle von 'T'. Die erste Methode gibt es nicht im Buch.
quelle
Soweit ich weiß, ermöglicht der Platzhalter einen präziseren Code in Situationen, in denen ein Typparameter nicht erforderlich ist (z. B. weil auf mehrere Stellen verwiesen wird oder weil mehrere Grenzen erforderlich sind, wie in anderen Antworten beschrieben).
In dem Link, den Sie angeben, habe ich (unter "Generische Methoden") die folgenden Aussagen gelesen, die in diese Richtung weisen:
quelle
Der zweite Weg ist etwas ausführlicher, aber Sie können sich darauf beziehen
T
:for (T shape : shapes) { ... }
Soweit ich weiß, ist das der einzige Unterschied.
quelle