Was ist der Unterschied zwischen <? erweitert Base> und <T erweitert Base>?


29

In diesem Beispiel:

import java.util.*;

public class Example {
    static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    static void function(List<? extends Number> outer)
    {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

doesntCompile() Kompiliert nicht mit:

Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
        doesntCompile(new HashMap<Integer, List<Integer>>());
                      ^

while compiles()wird vom Compiler akzeptiert.

Diese Antwort erklärt , dass der einzige Unterschied ist , dass im Gegensatz zu <? ...>, <T ...>können Sie die Art Referenz später, das scheint nicht der Fall zu sein.

Was ist der Unterschied zwischen <? extends Number>und <T extends Number>in diesem Fall und warum wird die erste Kompilierung nicht durchgeführt?


Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Samuel Liew

[1] Java Generischer Typ: Unterschied zwischen Liste <? erweitert Nummer> und Liste <T erweitert Nummer> scheint die gleiche Frage zu stellen, aber obwohl es von Interesse sein mag, ist es nicht wirklich ein Duplikat. [2] Obwohl dies eine gute Frage ist, spiegelt der Titel die spezifische Frage, die im letzten Satz gestellt wird, nicht richtig wider.
Skomisa


Wie wäre es mit der Erklärung hier ?
Jrook

Antworten:


14

Durch Definieren der Methode mit der folgenden Signatur:

static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

und aufrufen wie:

compiles(new HashMap<Integer, List<Integer>>());

In jls §8.1.2 finden wir, dass (interessanter Teil von mir fett gedruckt):

Eine generische Klassendeklaration definiert eine Reihe parametrisierter Typen (§4.5), einen für jeden möglichen Aufruf des Typparameterabschnitts durch Typargumente . Alle diese parametrisierten Typen haben zur Laufzeit dieselbe Klasse.

Mit anderen Worten, der Typ Twird mit dem Eingabetyp abgeglichen und zugewiesen Integer. Die Signatur wird effektiv static void compiles(Map<Integer, List<Integer>> map).

Wenn es um doesntCompileMethoden geht, definiert jls Regeln für die Untertypisierung ( §4.5.1 , von mir fett gedruckt):

Ein Typargument T1 soll ein anderes Typargument T2 enthalten, geschrieben T2 <= T1, wenn die mit T2 bezeichnete Menge von Typen nachweislich eine Teilmenge der mit T1 bezeichneten Typmenge unter dem reflexiven und transitiven Abschluss der folgenden Regeln ist ( wobei <: die Untertypisierung bezeichnet (§4.10)):

  • ? erweitert T <=? erweitert S, wenn T <: S.

  • ? erweitert T <=?

  • ? Super T <=? super S wenn S <: T.

  • ? Super T <=?

  • ? Super T <=? erweitert Objekt

  • T <= T.

  • T <=? verlängert T.

  • T <=? Super T.

Dies bedeutet, dass ? extends Numbertatsächlich enthält Integeroder sogar List<? extends Number>enthält List<Integer>, aber es ist nicht der Fall für Map<Integer, List<? extends Number>>und Map<Integer, List<Integer>>. Mehr zu diesem Thema finden Sie in diesem SO-Thread . Sie können die Version mit ?Platzhalter weiterhin zum Laufen bringen, indem Sie deklarieren, dass Sie einen Subtyp von List<? extends Number>:

public class Example {
    // now it compiles
    static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    public static void main(String[] args) {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

[1] Ich denke du meintest ? extends Numbereher als ? extends Numeric. [2] Ihre Behauptung, dass "dies bei List <? Extend Number> und List <Integer> nicht der Fall ist ", ist falsch. Wie @VinceEmigh bereits betont hat, können Sie eine Methode erstellen static void demo(List<? extends Number> lst) { }und so demo(new ArrayList<Integer>());oder so aufrufen demo(new ArrayList<Float>());, und der Code wird kompiliert und in Ordnung ausgeführt. Oder verstehe ich vielleicht falsch oder verstehe ich falsch, was Sie gesagt haben?
Skomisa

@ Du hast in beiden Fällen recht. Wenn es um Ihren zweiten Punkt geht, habe ich ihn irreführend geschrieben. Ich meinte List<? extends Number>als Typparameter die gesamte Karte, nicht sich selbst. Vielen Dank für den Kommentar.
Andronicus

@skomisa aus dem gleichen Grund List<Number>enthält nicht List<Integer>. Angenommen, Sie haben eine Funktion static void check(List<Number> numbers) {}. Wenn der Aufruf check(new ArrayList<Integer>());damit nicht kompiliert wird, müssen Sie die Methode als definieren static void check(List<? extends Number> numbers) {}. Mit der Karte ist es das gleiche, aber mit mehr Verschachtelung.
Andronicus

1
@skomisa ist genau wie Numberein Typparameter der Liste und Sie müssen hinzufügen ? extends, um sie kovariant zu machen, List<? extends Number>ist ein Typparameter von Mapund benötigt auch ? extendsKovarianz.
Andronicus

1
OK. Da Sie die Lösung für einen mehrstufigen Platzhalter (auch als "verschachtelter Platzhalter" bezeichnet ?) Bereitgestellt und mit der entsprechenden JLS-Referenz verknüpft haben, verfügen Sie über die Prämie.
Skomisa

6

Im Anruf:

compiles(new HashMap<Integer, List<Integer>>());

T ist mit Integer abgeglichen, daher ist der Typ des Arguments a Map<Integer,List<Integer>>. Dies ist bei der Methode nicht der Fall doesntCompile: Der Typ des Arguments bleibt Map<Integer, List<? extends Number>>unabhängig vom tatsächlichen Argument im Aufruf. und das ist nicht abtretbar von HashMap<Integer, List<Integer>>.

AKTUALISIEREN

In der doesntCompileMethode hindert Sie nichts daran, Folgendes zu tun:

static void doesntCompile(Map<Integer, List<? extends Number>> map) {
    map.put(1, new ArrayList<Double>());
}

Es kann also offensichtlich kein HashMap<Integer, List<Integer>>als Argument akzeptieren .


Wie würde ein gültiger Anruf doesntCompiledann aussehen? Nur neugierig darauf.
Xtreme Biker

1
@XtremeBiker doesntCompile(new HashMap<Integer, List<? extends Number>>());würde genauso funktionieren wie doesntCompile(new HashMap<>());.
Skomisa

@XtremeBiker, auch das würde funktionieren, Map <Integer, List <? erweitert Number >> map = new HashMap <Integer, List <? erweitert Nummer >> (); map.put (null, neue ArrayList <Integer> ()); doesntCompile (map);
MOnkey

"das ist nicht zuweisbar von HashMap<Integer, List<Integer>>" Könnten Sie bitte erläutern, warum es nicht von ihm zuweisbar ist?
Dev Null

@ DevNull siehe mein Update oben
Maurice Perry

2

Vereinfachtes Demonstrationsbeispiel. Das gleiche Beispiel kann wie unten dargestellt werden.

static void demo(List<Pair<? extends Number>> lst) {} // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst) {} // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too

public static class Pair<T> {}
public static class SubPair<T> extends Pair<T> {}

List<Pair<? extends Number>>ist ein mehrstufiger Platzhaltertyp, während List<? extends Number>es sich um einen Standard-Platzhaltertyp handelt.

Gültige konkrete Instanziierungen des Platzhaltertyps List<? extends Number>umfassen Numberund alle Untertypen von, Numberwährend in diesem Fall List<Pair<? extends Number>>ein Typargument vom Typargument ist und selbst eine konkrete Instanziierung des generischen Typs aufweist.

Generika sind unveränderlich, sodass Pair<? extends Number>Wildcard-Typen nur akzeptiert werden können Pair<? extends Number>>. Der innere Typ ? extends Numberist bereits kovariant. Sie müssen den umschließenden Typ als Kovariante festlegen, um die Kovarianz zuzulassen.


Wie kommt es, dass <Pair<Integer>>es nicht funktioniert <Pair<? extends Number>>, aber funktioniert <T extends Number> <Pair<T>>?
jaco0646

@ jaco0646 Sie stellen im Wesentlichen die gleiche Frage wie das OP, und die Antwort von Andronicus wurde akzeptiert. Siehe das Codebeispiel in dieser Antwort.
Skomisa

@skomisa, ja, ich stelle die gleiche Frage aus mehreren Gründen: Zum einen scheint diese Antwort die Frage des OP nicht wirklich zu beantworten. aber zweitens finde ich diese Antwort leichter zu verstehen. Ich kann der Antwort von Andronicus in keiner Weise folgen, die mich dazu bringt, verschachtelte gegen nicht verschachtelte Generika oder sogar Tgegen zu verstehen ?. Ein Teil des Problems besteht darin, dass Andronicus, wenn er den entscheidenden Punkt seiner Erklärung erreicht, auf einen anderen Thread zurückgreift, der nur triviale Beispiele verwendet. Ich hatte gehofft, hier eine klarere und vollständigere Antwort zu bekommen.
jaco0646

1
@ jaco0646 OK. Das Dokument "Java Generics FAQs - Type Arguments" von Angelika Langer enthält eine FAQ mit dem Titel Was bedeuten mehrstufige (dh verschachtelte) Platzhalter? . Das ist die beste mir bekannte Quelle, um die in der Frage des OP aufgeworfenen Fragen zu erläutern. Die Regeln für verschachtelte Platzhalter sind weder einfach noch intuitiv.
Skomisa

1

Ich würde Ihnen empfehlen, in der Dokumentation allgemeiner Platzhalter nachzuschlagen, insbesondere in Richtlinien für die Verwendung von Platzhaltern

Ehrlich gesagt Ihre Methode #doesntCompile

static void doesntCompile(Map<Integer, List<? extends Number>> map) {}

und wie anrufen

doesntCompile(new HashMap<Integer, List<Integer>>());

Ist grundsätzlich falsch

Fügen wir die rechtliche Umsetzung hinzu:

    static void doesntCompile(Map<Integer, List<? extends Number>> map) {
        List<Double> list = new ArrayList<>();
        list.add(0.);
        map.put(0, list);
    }

Es ist wirklich in Ordnung, weil Double Number erweitert, also ist Put List<Double>absolut in Ordnung List<Integer>, oder?

Nehmen Sie jedoch immer noch an, dass es legal ist , hier new HashMap<Integer, List<Integer>>()von Ihrem Beispiel abzuweichen?

Der Compiler glaubt das nicht und tut sein Bestes, um solche Situationen zu vermeiden.

Versuchen Sie, die gleiche Implementierung mit der Methode #compile durchzuführen, und der Compiler erlaubt Ihnen offensichtlich nicht, eine Liste von Doubles in die Map einzufügen.

    static <T extends Number> void compiles(Map<Integer, List<T>> map) {
        List<Double> list = new ArrayList<>();
        list.add(10.);
        map.put(10, list); // does not compile
    }

Grundsätzlich können Sie nichts setzen, aber List<T>deshalb ist es sicher, diese Methode mit new HashMap<Integer, List<Integer>>()oder new HashMap<Integer, List<Double>>()oder new HashMap<Integer, List<Long>>()oder aufzurufen new HashMap<Integer, List<Number>>().

Kurz gesagt, Sie versuchen, mit dem Compiler zu schummeln, und dieser verteidigt sich ziemlich gegen solchen Schummel.

NB: Die Antwort von Maurice Perry ist absolut richtig. Ich bin mir nur nicht sicher, ob es klar genug ist, also habe ich versucht (ich hoffe wirklich, dass ich es geschafft habe), einen umfangreicheren Beitrag hinzuzufügen.

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.