Was ist ein reales Beispiel für generisches <? super T>?


76

Ich verstehe, dass dies <? super T>jede Superklasse von T(Elternklasse Tjeder Stufe) darstellt. Aber ich habe wirklich Mühe, mir ein Beispiel aus dem wirklichen Leben für diese generisch gebundene Wildcard vorzustellen.

Ich verstehe was <? super T>bedeutet und ich habe diese Methode gesehen:

public class Collections {
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      for (int i = 0; i < src.size(); i++)
        dest.set(i, src.get(i));
  }
}

Ich suche nach einem Beispiel für einen realen Anwendungsfall, in dem diese Konstruktion verwendet werden kann, und nicht nach einer Erklärung dessen, was sie ist.


18
Dies ist kein Duplikat, eine sehr gültige Frage
Eugene

6
Ich denke auch nicht, dass es ein Duplikat ist, er fragt nach konkreten Situationen, nicht nach dem Prinzip dahinter
ItFreak

2
Dies ist der Beginn der Antwort, die ich schreiben wollte, als diese geschlossen wurde: Ich stimme den engen Wählern in gewissem Maße zu: Die Antwort könnte mit einiger Sorgfalt von stackoverflow.com/questions/2723397/… abgeleitet werden. . Diese Frage (sowie die Antworten hier) konzentrieren sich jedoch auf das technische Prinzip. Ein einfaches, realistisches Beispiel dafür, wo die Verwendung sinnvoll ist, ? super Tkönnte hilfreich sein.
Marco13

4
Denken Sie nicht, dass dies als Duplikat hätte geschlossen werden sollen, da der Autor eher nach realen OOP-Modellen als nach ausführlichen Erklärungen zur Funktionsweise der Vererbung in Java fragt.
John Stark

3
Ist dieses Beispiel hier nicht ein realer Anwendungsfall?
user253751

Antworten:


49

Das einfachste Beispiel, an das ich denken kann, ist:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

aus dem gleichen genommen Collections. Auf diese Weise Dogkann a implementieren Comparable<Animal>und wenn dies Animalbereits implementiert ist, Dogmuss nichts getan werden.

EDIT für ein echtes Beispiel:

Nach ein paar E-Mail-Ping-Pongs darf ich ein echtes Beispiel von meinem Arbeitsplatz aus präsentieren (yay!).

Wir haben eine Schnittstelle namens Sink(es spielt keine Rolle, was sie tut), die Idee ist, dass sich Dinge ansammeln . Die Erklärung ist ziemlich trivial (vereinfacht):

interface Sink<T> {
    void accumulate(T t);
}

Offensichtlich gibt es eine Hilfsmethode, die a nimmt Listund seine Elemente in a entleert Sink(es ist etwas komplizierter, aber um es einfach zu machen):

public static <T> void drainToSink(List<T> collection, Sink<T> sink) {
    collection.forEach(sink::accumulate);
}

Das ist einfach richtig? Gut...

Ich kann ein haben List<String>, aber ich möchte es auf ein ablassen Sink<Object>- das ist eine ziemlich häufige Sache, die wir für uns tun müssen; aber das wird scheitern:

Sink<Object> sink = null;
List<String> strings = List.of("abc");
drainToSink(strings, sink);

Damit dies funktioniert, müssen wir die Erklärung ändern in:

public static <T> void drainToSink(List<T> collection, Sink<? super T> sink) {
    ....
}

1
Das Kotlin- Tutorial verwendet Sourceals Beispiel ... das ist im Grunde das Doppelte Ihres SinkBeispiels.
Bakuriu

2
Sicherlich könnten Sie es anders definieren, indem Sie die Liste erstellen? erweitert T?
Weckar E.

@WeckarE. Nicht, wenn die Methode in der Liste selbst enthalten ist.
Setzen Sie Monica

1
@WeckarE. Ich könnte ja, aber das würde die Semantik ein wenig verändern, jetzt kann ich nichts hinzufügen list, es wird nur ein Produzent; Wie gesagt, dies ist ein vereinfachtes Beispiel ...
Eugene

17

Angenommen, Sie haben diese Klassenhierarchie: Katze erbt von Mammal, was wiederum von Animal erbt.

List<Animal> animals = new ArrayList<>();
List<Mammal> mammals = new ArrayList<>();
List<Cat> cats = ...

Diese Aufrufe sind gültig:

Collections.copy(animals, mammals); // all mammals are animals
Collections.copy(mammals, cats);    // all cats are mammals
Collections.copy(animals, cats);    // all cats are animals
Collections.copy(cats, cats);       // all cats are cats 

Diese Aufrufe sind jedoch nicht gültig:

Collections.copy(mammals, animals); // not all animals are mammals
Collections.copy(cats, mammals);    // not all mammals are cats
Collections.copy(cats, animals);    // mot all animals are cats

Die Methodensignatur stellt also einfach sicher, dass Sie von einer spezifischeren Klasse (in der Vererbungshierarchie niedriger) in eine allgemeinere Klasse (in der Vererbungshierarchie höher) kopieren und nicht umgekehrt.


2
Das superSchlüsselwort ist jedoch nicht erforderlich, damit dies funktioniert. Diese Unterschrift hätte auch identisches Verhalten aufgedeckt: public static <T> void copy(List<T> dest, List<? extends T> src) {
Nick

1
@ Nick Guter Fang. Warum dann diese Unterschrift? Diese Frage sollte Java-Sprachdesignern gestellt werden. Bisher habe ich nur festgestellt, dass Sie Code schreiben können wie: Collections.<Mammal>copy(animals, cats);- aber ich weiß nicht, warum jemand Code wie diesen schreiben würde / sollte ...
Benoit

6

Schauen Sie sich zum Beispiel die Collections.addAllMethodenimplementierung an:

public static <T> boolean addAll(Collection<? super T> c, T... elements) {
    boolean result = false;
    for (T element : elements)
        result |= c.add(element);
    return result;
}

Hier können die Elemente in jede Sammlung eingefügt werden, deren Elementtyp ein Supertyp des TElementtyps ist.

Ohne Platzhalter mit niedrigerer Grenze:

public static <T> boolean addAll(Collection<T> c, T... elements) { ... }

Folgendes wäre ungültig gewesen:

List<Number> nums = new ArrayList<>();
Collections.<Integer>addAll(nums , 1, 2, 3);

weil der Begriff Collection<T>restriktiver ist als Collection<? super T>.


Ein anderes Beispiel:

Predicate<T>Schnittstelle in Java, die einen <? super T>Platzhalter in den folgenden Methoden verwendet:

default Predicate<T> and(Predicate<? super T> other);

default Predicate<T>  or(Predicate<? super T> other);

<? super T> ermöglicht es, einen größeren Bereich verschiedener Prädikate zu verketten, zum Beispiel:

Predicate<String> p1 = s -> s.equals("P");
Predicate<Object> p2 = o -> o.equals("P");

p1.and(p2).test("P"); // which wouldn't be possible with a Predicate<T> as a parameter

Ich denke nicht, dass dieses Beispiel besonders überzeugend ist. Wenn Sie die explizite Instanziierung der Typvariablen weglassen, können Sie genauso gut Collections.addAll(nums, 1, 2, 3)mit beiden Methodensignaturen schreiben .
Nick

2

Angenommen, Sie haben eine Methode:

passToConsumer(Consumer<? super SubType> consumer)

dann rufen Sie diese Methode mit jeder auf, Consumerdie verbrauchen kann SubType:

passToConsumer(Consumer<SuperType> superTypeConsumer)
passToConsumer(Consumer<SubType> subTypeConsumer)
passToConsumer(Consumer<Object> rootConsumer)

Zum Beispiel:

class Animal{}

class Dog extends Animal{

    void putInto(List<? super Dog> list) {
        list.add(this);
    }
}

So kann ich das Dogin List<Animal>oder setzen List<Dog>:

List<Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();

Dog dog = new Dog();
dog.putInto(dogs);  // OK
dog.putInto(animals);   // OK

Wenn Sie die putInto(List<? super Dog> list)Methode ändern in putInto(List<Animal> list):

Dog dog = new Dog();

List<Dog> dogs = new ArrayList<>();
dog.putInto(dogs);  // compile error, List<Dog> is not sub type of List<Animal>

oder putInto(List<Dog> list):

Dog dog = new Dog();

List<Animal> animals = new ArrayList<>();
dog.putInto(animals); // compile error, List<Animal> is not sub type of List<Dog>

1
Wann immer Sie sagen Consumeroder consumedies automatisch in die PECSKategorie fällt , ohne zu sagen, dass dies schlecht ist, gute Beispiele
Eugene

1

Ich habe ein Webradio geschrieben, also hatte ich die Klasse MetaInformationObject, die die Superklasse für PLS- und M3U-Wiedergabelisten war. Ich hatte einen Auswahldialog, also hatte ich:

public class SelectMultipleStreamDialog <T extends MetaInformationObject>
public class M3UInfo extends MetaInformationObject
public class PLSInfo extends MetaInformationObject

Diese Klasse hatte eine Methode public T getSelectedStream().
Der Anrufer erhielt also ein T vom konkreten Typ (PLS oder M3U), musste aber an der Oberklasse arbeiten, sodass es eine Liste gab : List<T super MetaInformationObject>. wo das Ergebnis hinzugefügt wurde.
So könnte ein generischer Dialog mit den konkreten Implementierungen umgehen und der Rest des Codes könnte in der Oberklasse funktionieren.
Hoffe das macht es etwas klarer.


1

Betrachten Sie dieses einfache Beispiel:

List<Number> nums = Arrays.asList(3, 1.2, 4L);
Comparator<Object> numbersByDouble = Comparator.comparing(Object::toString);
nums.sort(numbersByDouble);

Hoffentlich ist dies ein etwas überzeugender Fall: Sie könnten sich vorstellen, die Zahlen zu Anzeigezwecken sortieren zu wollen (für die der toString eine vernünftige Reihenfolge darstellt), aber Numberselbst nicht vergleichbar.

Dies wird kompiliert, weil integers::sorta Comparator<? super E>. Wenn es nur ein Comparator<E>(wo Ein diesem Fall ist Number) dauert, kann der Code nicht kompiliert werden, da Comparator<Object>es sich nicht um einen Subtyp von handelt Comparator<Number>(aus Gründen, die Ihre Frage angibt, dass Sie bereits verstanden haben, werde ich nicht darauf eingehen).


1

Hier dienen Sammlungen als gutes Beispiel.

Wie in 1 angegeben , List<? super T>können Sie ListElemente vom Typ erstellen , die weniger abgeleitet sind als T, sodass Elemente enthalten werden, die vom TTyp erben, vom Typ Tund Tvom erben.

Auf der anderen Seite List<? extends T>können Sie ein definieren List, das nur Elemente enthalten kann, von denen geerbt wird T(in einigen Fällen nicht einmal vom Typ T).

Dies ist ein gutes Beispiel:

public class Collections {
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      for (int i = 0; i < src.size(); i++)
        dest.set(i, src.get(i));
  }
}

Hier möchten Sie einen Listweniger abgeleiteten Typ auf einen Listweniger abgeleiteten Typ projizieren . Hier List<? super T>versichert uns, dass alle Elemente von srcin der neuen Kollektion gültig sind.

1 : Unterschied zwischen <? Super T> und <? erweitert T> in Java


0

Angenommen, Sie haben:

class T {}
class Decoder<T>
class Encoder<T>

byte[] encode(T object, Encoder<? super T> encoder);    // encode objects of type T
T decode(byte[] stream, Decoder<? extends T> decoder);  // decode a byte stream into a type T

Und dann:

class U extends T {}
Decoder<U> decoderOfU;
decode(stream, decoderOfU);     // you need something that can decode into T, I give you a decoder of U, you'll get U instances back

Encoder<Object> encoderOfObject;
encode(stream, encoderOfObject);// you need something that can encode T, I give you something that can encode all the way to java.lang.Object

0

Hierfür fallen einige Beispiele aus dem wirklichen Leben ein. Das erste, das ich ansprechen möchte, ist die Idee, dass ein reales Objekt für "improvisierte" Funktionen verwendet wird. Stellen Sie sich vor, Sie haben einen Steckschlüssel:

public class SocketWrench <T extends Wrench>

Der offensichtliche Zweck eines Steckschlüssels ist es also, als Wrench. Wenn Sie jedoch bedenken, dass ein Schraubenschlüssel zur Not verwendet werden kann, um einen Nagel einzuschlagen, können Sie eine Vererbungshierarchie haben, die folgendermaßen aussieht:

public class SocketWrench <T extends Wrench>
public class Wrench extends Hammer

In diesem Szenario können Sie anrufen socketWrench.pound(Nail nail = new FinishingNail()), obwohl dies als atypische Verwendung für a angesehen wird SocketWrench.

Während der gesamten Zeit SocketWrenchhätte der Zugriff darauf, Methoden aufrufen zu können, als applyTorque(100).withRotation("clockwise").withSocketSize(14)ob sie als SocketWrenchstatt nur als Wrench, statt als verwendet würden Hammer.


Ich finde das nicht sehr überzeugend: Warum sollte SocketWrench ein Generikum sein?
Max

Sie können alle Arten von Steckschlüsseltypen verwenden: 1/4 ”-Antrieb, 1/2” -Antrieb, einstellbare Griffwinkel-Steckschlüssel, Drehmomentschlüssel, unterschiedliche Anzahl von Ratschenzähnen usw. Wenn Sie jedoch nur einen Schraubenschlüssel benötigen, der Ratschen verwendet, können Sie ihn verwenden ein generischer Steckschlüssel anstelle eines speziellen 32-Zahn-Winkelsteckers mit 3/4 Zoll Antrieb.
John Stark
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.