Java Generics Super Keyword

75

Ich habe diese Themen durchgearbeitet

Ich scheine jedoch immer noch irgendwie mit dem superSchlüsselwort verloren zu sein :

  1. Wenn wir eine solche Sammlung deklarieren:

    List<? super Number> list = null;
    list.add(new Integer(0)); // this compiles
    list.add(new Object()); // this doesn't compile
    

sollte es nicht das Gegenteil sein - wir haben eine Liste, die einige Objekte (unbekannten Typs) enthält, deren Eltern Eltern sind Number. Also Objectsollte passen (da es das Elternteil von ist Number) und Integersollte nicht. Das Gegenteil ist aus irgendeinem Grund der Fall.

  1. Vorausgesetzt, wir haben den folgenden Code

    static void test(List<? super Number> param) {
      param.add(new Integer(2));
    }
    
    public static void main(String[] args) {
      List<String> sList = new ArrayList<String>();
      test(sList);            // will never compile, however...
    }
    

Es ist unmöglich, den obigen Code zu kompilieren (und meine Vernunft legt nahe, dass dies das richtige Verhalten ist), aber die grundlegende Logik könnte das Gegenteil beweisen:

String is Object, Object is superclass of Number. So String should work.

Ich weiß, das ist verrückt, aber ist das nicht der Grund, warum sie keine <S super T>Konstrukte zugelassen haben? Wenn ja, warum <? super T>ist das dann erlaubt?

Könnte mir jemand helfen, den fehlenden Teil dieser Logikkette wiederherzustellen?

Denis Kniazhev
quelle

Antworten:

97

Der begrenzte Platzhalter in List<? super Number>kann Numberjeden seiner Supertypen erfassen . Da Number extends Object implements Serializablebedeutet dies , dass die einzigen Arten , die von derzeit Capture-umwandelbar sind , List<? super Number>sind:

  • List<Number>
  • List<Object>
  • List<Serializable>

Beachten Sie, dass Sie add(Integer.valueOf(0))einen der oben genannten Typen auswählen können. Sie können jedoch nicht add(new Object()) zu a List<Number>oder a wechselnList<Serializable> , da dies gegen die generische Sicherheitsregel verstößt.

Daher ist es NICHT wahr, dass Sie addeinen Supertyp von Numbera können List<? super Number>; So funktionieren begrenzte Wildcard- und Capture-Konvertierungen einfach nicht. Sie deklarieren kein, List<? super Number>weil Sie möglicherweise ein hinzufügen möchten Object(Sie können nicht!); Sie tun dies, weil Sie NumberObjekte hinzufügen möchten (dh es ist ein "Verbraucher" von Number), und einfach a List<Number>ist zu restriktiv.

Verweise

Siehe auch

  • Effektive Java 2nd Edition , Punkt 28: Verwenden Sie begrenzte Platzhalter, um die API-Flexibilität zu erhöhen
    • "PECS steht für Produzent- extends, Konsumenten-super

Verwandte Fragen

  • Zu viele zum Auflisten, PECS, new Integer(0)vs valueOfusw.
Polygenschmierstoffe
quelle
16
Ich kann nicht bestätigen, ob Ihre Antwort richtig oder falsch ist, weil sie zu verwirrend ist und zu viel Wissen voraussetzt. Diese Frage würde nicht gestellt werden, wenn wir eine solche Antwort verstehen könnten!
Alex Worden
@AlexWorden diese Antwort ist technisch sehr korrekt, keine Frage, wenn Sie eine leichte Erklärung wollen, kann dies helfen
Eugene
22

Der erste Teil List<Number>passt hinein, List<? super Number>aber Sie können kein Objectzu einem hinzufügen List<Number>. Aus diesem Grund kann man nicht hinzufügen Objectzu List<? super Number>.

Auf der anderen Seite können Sie jede Unterklasse von Number( Numberenthalten) zu Ihrer Liste hinzufügen .

Für den zweiten Teil Stringist eine Object, aber Stringkeine Oberklasse von Number.

Wenn es so funktionieren würde, da jede Klasse eine Unterklasse von ist Object, superhätte es keine Bedeutung.


Sehen wir uns alle möglichen Fälle an mit List<? super Number>:


  • Die übergebene Liste ist a List<Object>
    • List<Object> wird funktionieren
    • Object passt <? super Number>
    • Sie können einen beliebigen Subtyp von Numberzu hinzufügenList<Object>
    • Selbst wenn Sie es auch hinzufügen könnten, sind StringSie sich nur sicher, dass Sie eine beliebige Unterklasse von hinzufügen können Number.

  • Die übergebene Liste ist eine List<Number>:
    • List<Number> wird funktionieren
    • Number passt <? super Number>
    • Sie können einen beliebigen Subtyp von Numberzu hinzufügenList<Number>

  • Die übergebene Liste ist eine List<Integer>(oder eine beliebige Unterklasse von Number):
    • List<Integer> wird nicht funktionieren
    • Integer ist eine Unterklasse von, Numberalso ist es genau das, was wir vermeiden wollen
    • Selbst wenn ein Integerin ein passt, können NumberSie keine Unterklasse von Numberin List<Integer>(zum Beispiel a Float) hinzufügen.
    • super bedeutet nicht eine Unterklasse.

  • Die übergebene Liste ist eine List<String>(oder eine Klasse, die sich weder erweitert Numbernoch in der "Superhierarchie" von Number(dh Numberund Object) befindet:
    • List<String> wird nicht funktionieren
    • Stringpasst nicht in Number"Superhierarchie"
    • Auch wenn Stringpasst in Object(die eine Superklasse Number) Sie woudln't sicher ein hinzufügen zu können , werden Numberauf eine , Listdie jede Unterklasse von einer der Superklassen enthalten Number)
    • super bedeutet nicht, dass eine Unterklasse einer der Superklassen vorliegt, sondern nur eine der Superklassen.

Wie funktioniert es ?

Sie können sagen, dass das Schlüsselwort respektiert wird , solange Sie eine Unterklasse von Numbermit Ihrer Eingabe hinzufügen können .Listsuper

Colin Hebert
quelle
1
@Vuntic, es ist ziemlich schwer, mit Generika vollkommen klar zu sein, aber ich habe meine Antwort aktualisiert und es versucht :)
Colin Hebert
1
Danke Colin, das hat es mir klarer gemacht. Dieses Thema muss ein wenig experimentiert werden, um es vollständig zu verstehen
Denis Kniazhev
Das ist eine gute Möglichkeit, Genercis zu erklären und alle möglichen Szenarien anzugeben, aber Sie könnten einige Farben und fette Formatierungen hinzufügen, um die Lesbarkeit zu verbessern;)
Andrzej Rehmann
6

Ich habe es eine Weile nicht verstanden. Viele der Antworten hier und die anderen Fragen zeigen speziell, wann und wo bestimmte Verwendungen Fehler sind, aber nicht so sehr warum.

So habe ich es endlich verstanden. Wenn ich eine Funktion habe, die Numbers zu a hinzufügt List, möchte ich sie möglicherweise vom Typ hinzufügen, der MySuperEfficientNumbermeine eigene benutzerdefinierte Klasse ist, die implementiert Number(aber keine Unterklasse von Integer). Jetzt weiß der Anrufer vielleicht nichts darüber MySuperEfficientNumber, aber solange er weiß, dass er die zur Liste hinzugefügten Elemente als nichts Spezifischeres behandelt Number, ist alles in Ordnung.

Wenn ich meine Methode wie folgt deklariert habe:

public static void addNumbersToList(List<? extends Number> numbers)

Dann könnte der Anrufer eine übergeben List<Integer>. Wenn meine Methode a hinzugefügt MySuperEfficientNumberzu Ende numbers, dann würde der Anrufer nicht mehr über einen Listvon Integers und der folgenden Code nicht funktionieren würde:

List<Integer> numbers = new ArrayList<Integer>();
addNumbersToList(numbers);

// The following would return a MySuperEfficientNumber not an Integer
Integer i = numbers.get(numbers.size()-1)

Offensichtlich kann das nicht funktionieren. Und der Fehler würde innerhalb der addNumbersToListMethode liegen. Sie würden so etwas bekommen wie:

The method add... is not applicable for the arguments (MySuperEfficientNumber)

Denn es numberskönnte sich um eine bestimmte Art handeln Number, die nicht unbedingt MySuperEfficientNumberkompatibel ist. Wenn ich die Deklaration umdrehen superwürde, würde die Methode ohne Fehler kompiliert, aber der Code des Aufrufers würde fehlschlagen mit:

The method addNumbersToList(List<? super Number>)... is not applicable for the arguments (List<Integer>)

Weil meine Methode sagt: "Glaube nicht, dass du Listetwas Spezifischeres sein kannst als Number. Ich könnte Numberder Liste alle möglichen seltsamen Dinge hinzufügen , du musst dich nur darum kümmern. Wenn du an sie denken willst als etwas noch allgemeineres als Number- wie Object- das ist in Ordnung, ich garantiere, dass sie mindestens Numbers sein werden, aber Sie können sie allgemeiner behandeln, wenn Sie wollen. "

Während extendssagt: "Es ist mir egal, welche Art von ListIhnen mir geben, solange jedes Element mindestens ein ist Number. Es kann jede Art von Number, sogar Ihre eigenen seltsamen, benutzerdefinierten, Numbererfundenen sein. Solange Sie implementieren diese Schnittstelle, wir sind gut. Ich werde Ihrer Liste nichts hinzufügen, da ich nicht weiß, welchen konkreten Typ Sie dort tatsächlich verwenden. "

nur keiner
quelle
4

List<? super Number> bedeutet, dass der Referenztyp der Variablen darauf hindeutet, dass wir eine Liste von Zahlen, Objekten oder Serializables haben.

Der Grund, warum Sie kein Objekt hinzufügen können, liegt darin, dass der Compiler nicht weiß, WELCHE dieser Klassen in der generischen Definition des tatsächlich instanziierten Objekts enthalten sind. Daher können Sie nur Number oder Subtypen von Number wie Double, Integer und übergeben demnächst.

Angenommen, wir haben eine Methode, die a zurückgibt List<? super Number>. Die Erstellung des Objekts innerhalb der Methode ist aus unserer Sicht gekapselt. Wir können nur nicht sagen, ob es so etwas ist:

List<? super Number> returnValue = new LinkedList<Object>();

oder

List<? super Number> returnValue = new ArrayList<Number>();

Der generische Typ könnte also Objekt oder Nummer sein. In beiden Fällen dürfen wir Number hinzufügen, aber nur in einem Fall dürfen wir Object hinzufügen.

In dieser Situation müssen Sie zwischen dem Referenztyp und dem tatsächlichen Objekttyp unterscheiden.

AbstaubBaer
quelle
2

List<? super Number>ist so ein Ort, an List<AncestorOfNumber>dem wir implizit jeden Numberauf seinen Supertyp umwandeln können AncestorOfNumber.

Beachten Sie ????Folgendes : Welcher generische Typ muss im folgenden Beispiel vorhanden sein?

InputStream mystream = ...;

void addTo(List<????> lsb) {
    lsb.add(new BufferedInputStream(mystream));
}

List<BufferedInputStream> lb = new ArrayList<>();
List<InputStream> li = new ArrayList<>();
List<Object> lo = new ArrayList<>();

...
{ addTo(lb); addTo(li); addTo(lo); }

Die Antwort: ????ist alles, worauf wir werfen können BufferedInputStream, was genau dasselbe oder einer seiner Vorfahren ist:? super BufferedInputStream

Comonad
quelle
2

Darf ich ein sehr einfaches Beispiel geben?

public void add(List<? super Number> list) {
}

Dies ermöglicht diese Anrufe

add(new LinkedList<Number>());

und alles über Nummer wie

add(new LinkedList<Object>());

aber nichts unterhalb der Hierarchie also nicht

add(new LinkedList<Double>());

oder

add(new LinkedList<Integer>());

Da es für das Programm nicht klar ist, ob Sie eine Liste mit Nummer oder Objekt angeben, kann der Compiler nicht zulassen, dass Sie etwas über Nummer hinzufügen.

Zum Beispiel würde eine Liste kein Objekt akzeptieren, obwohl ein Objekt eine Nummer akzeptieren würde. Da dies jedoch nicht klar ist, wäre die einzige gültige Eingabe die Zahl und ihre Untertypen.

Anna Klein
quelle
1

Es gibt zwei Winkel hier: was Sie setzen in eine Sammlung und was Sie bekommen aus einer Sammlung, wenn begrenzt Typen beteiligt sind.


Schauen wir uns zuerst den ? extends NumberFall an. Wenn eine Sammlung mit solchen Grenzen definiert ist, wissen wir Folgendes: Jedes Element hat eine Obergrenze als Number. Wir kennen den genauen Typ nicht (könnte ein sein Integer/Long/etc), aber wir wissen sicher, dass seine Obergrenze ist Number.

Das Lesen aus einer solchen Sammlung bringt uns also eine Number. Dies ist der einzige garantierte Typ, den wir daraus erhalten können.

Das Schreiben in eine solche Sammlung ist verboten. Aber wieso? Habe ich das nicht gesagt, während wir lesen - wir werden immer eine bekommen Number, warum also das Schreiben verbieten? Die Situation ist hier etwas komplizierter:

 List<Integer> ints = ....;
 List<? extends Number> numbers = ints;
 numbers.add(12D); // add a double in here

Wenn das Hinzufügen erlaubt gewesen wäre, hätten numbersSie effektiv ein Doublein a hinzufügen können List of Integers.


Nun zu Ihrem Beispiel:

 List<? super Number> list = null;
 list.add(new Integer(0));
 list.add(new Object());

Wir wissen, listdass es zum Beispiel einen bestimmten Supertyp von enthält .NumberObject

Das Lesen von einer solchen Liste würde uns einen bestimmten Typ geben X, von dem Xein Elternteil wäre Number. Was wäre das? Du kannst es nicht wirklich wissen. Es könnte theoretisch MyNumber extends Numberoder viel einfacher sein: an Object. Da Sie nicht sicher wissen können, wäre die einzige sichere Sache, aus der Sie lesen können, der Supertyp von allem - Object.

Was ein bisschen komisch ist, kann sein:

List<? super String> list = ...;
String s = list.get(0); // fails, compiler does not care that String is final

Das Schreiben ist etwas komplizierter, aber nur geringfügig. Denken Sie daran, was wir wissen list: Es ist ein Typ, der Number erweitert / implementiert wird (wenn es eine Schnittstelle wäre), sodass Sie diesem Supertyp immer einen Subtyp (oder sich Numberselbst) zuweisen können .

             Some type X
                 / \
                  |
                Number
                 / \
                  |
    Some type Y that we an put in here
Eugene
quelle