Qual è la differenza tra <? estende Base> e <T estende Base>?


29

In questo esempio:

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() non riesce a compilare con:

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>>());
                      ^

mentre compiles()è accettato dal compilatore.

Questa risposta spiega che l'unica differenza è che a differenza <? ...>,<T ...> consente di fare riferimento al tipo in un secondo momento, il che non sembra essere il caso.

Qual è la differenza tra <? extends Number>e <T extends Number>in questo caso e perché la prima compilazione?


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Samuel Liew

[1] Java Tipo generico: differenza tra Elenco <? estende il numero> e l'elenco <T estende il numero> sembra porre la stessa domanda, ma sebbene possa essere interessante, in realtà non è un duplicato. [2] Sebbene questa sia una buona domanda, il titolo non riflette correttamente la domanda specifica che viene posta nella frase finale.
skomisa


Che ne dici della spiegazione qui ?
jrook

Risposte:


14

Definendo il metodo con la seguente firma:

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

e invocandolo come:

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

Nel jls §8.1.2 troviamo che (parte interessante in grassetto da me):

Una dichiarazione di classe generica definisce un insieme di tipi parametrizzati (§4.5), uno per ogni possibile invocazione della sezione dei parametri di tipo mediante argomenti di tipo . Tutti questi tipi con parametri condividono la stessa classe in fase di esecuzione.

In altre parole, il tipo Tviene confrontato con il tipo di input e assegnato Integer. La firma diventerà effettivamentestatic void compiles(Map<Integer, List<Integer>> map) .

Quando si tratta di doesntCompilemetodo, jls definisce le regole del sottotipo ( §4.5.1 , in grassetto da me):

Si dice che un argomento di tipo T1 contenga un altro argomento di tipo T2, scritto T2 <= T1, se l'insieme di tipi indicato da T2 è di fatto un sottoinsieme dell'insieme di tipi indicato da T1 sotto la chiusura riflessiva e transitiva delle seguenti regole ( dove <: indica il sottotipo (§4.10)):

  • ? estende T <=? estende S se T <: S

  • ? estende T <=?

  • ? super T <=? super S se S <: T

  • ? super T <=?

  • ? super T <=? estende l'oggetto

  • T <= T

  • T <=? estende T

  • T <=? super T

Ciò significa che ? extends Numbereffettivamente contiene Integero addirittura List<? extends Number>contiene List<Integer>, ma non è il caso di Map<Integer, List<? extends Number>>e Map<Integer, List<Integer>>. Ulteriori informazioni su questo argomento sono disponibili in questo thread SO . Puoi comunque far funzionare la versione con ?caratteri jolly dichiarando che ti aspetti un sottotipo di 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] Penso che volevi dire ? extends Numberpiuttosto che ? extends Numeric. [2] La tua affermazione che "non è il caso dell'elenco <? Estende il numero> e dell'elenco <Intero>" non è corretta. Come ha già sottolineato @VinceEmigh, puoi creare un metodo static void demo(List<? extends Number> lst) { }e chiamarlo come questo demo(new ArrayList<Integer>());o questo demo(new ArrayList<Float>());, e il codice viene compilato ed eseguito correttamente. O sto forse fraintendendo o fraintendendo quello che hai affermato?
skomisa

@ Hai ragione in entrambi i casi. Quando si tratta del secondo punto, l'ho scritto in modo fuorviante. Intendevo List<? extends Number>come parametro di tipo dell'intera mappa, non se stesso. Grazie mille per il commento
Andronico

@skomisa dallo stesso motivo List<Number> non contiene List<Integer>. Supponi di avere una funzione static void check(List<Number> numbers) {}. Quando si richiama con check(new ArrayList<Integer>());esso non si compila, è necessario definire il metodo come static void check(List<? extends Number> numbers) {}. Con la mappa è lo stesso ma con più nidificazione.
Andronico

1
@skomisa così come Numberè un parametro di tipo dell'elenco e devi aggiungere? extends per renderlo covariante, List<? extends Number>è un parametro di tipo Mape necessita anche ? extendsdi covarianza.
Andronico

1
OK. Dal momento che hai fornito la soluzione per un carattere jolly multi livello (aka "carattere jolly nidificato" ?) E collegato al riferimento JLS pertinente, ottieni la generosità.
skomisa,

6

Nella chiamata:

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

T è abbinato a Integer, quindi il tipo di argomento è a Map<Integer,List<Integer>>. Non è il caso del metodo doesntCompile: il tipo di argomento rimane Map<Integer, List<? extends Number>>qualunque sia l'argomento effettivo nella chiamata; e che non è assegnabile da HashMap<Integer, List<Integer>>.

AGGIORNARE

Nel doesntCompilemetodo, nulla ti impedisce di fare qualcosa del genere:

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

Quindi, ovviamente, non può accettare a HashMap<Integer, List<Integer>>come argomento.


Come sarebbe allora una chiamata valida doesntCompile? Solo curioso di questo.
Xtreme Biker,

1
@XtremeBiker doesntCompile(new HashMap<Integer, List<? extends Number>>());avrebbe funzionato, come avrebbe funzionato doesntCompile(new HashMap<>());.
skomisa

@XtremeBiker, anche questo funzionerebbe, Map <Integer, List <? estende Numero >> map = new HashMap <Numero intero, Elenco <? estende il numero >> (); map.put (null, new ArrayList <Integer> ()); doesntCompile (mappa);
MOnkey

"che non è assegnabile da HashMap<Integer, List<Integer>>" potresti per favore spiegare perché non è assegnabile da esso?
Dev Null

@DevNull vedi il mio aggiornamento sopra
Maurice Perry

2

Esempio semplificato di dimostrazione. Lo stesso esempio può essere visualizzato come di seguito.

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>> è un tipo di carattere jolly a più livelli mentre List<? extends Number> è un tipo di carattere jolly standard.

Sono List<? extends Number>incluse le istanze concrete del tipo di jolly Numbere tutti i sottotipi di Numbermentre in caso diList<Pair<? extends Number>> cui si tratti di un argomento di tipo di argomento di tipo e esso stesso abbia un'istanza concreta di tipo generico.

I generici sono invarianti, quindi Pair<? extends Number>il tipo di carattere jolly può solo accettare Pair<? extends Number>>. Il tipo interno ? extends Numberè già covariante. Devi rendere il tipo allegato come covariante per consentire la covarianza.


In che <Pair<Integer>>modo non funziona <Pair<? extends Number>>ma funziona <T extends Number> <Pair<T>>?
jaco0646,

@ jaco0646 In sostanza stai ponendo la stessa domanda del PO e la risposta di Andronico è stata accettata. Vedi l'esempio di codice in quella risposta.
skomisa,

@skomisa, sì, sto facendo la stessa domanda per un paio di ragioni: una è che questa risposta non sembra in realtà rispondere alla domanda del PO; ma due è che trovo questa risposta più facile da comprendere. Non riesco a seguire la risposta di Andronico in alcun modo che mi induca a comprendere generici nidificati contro non nidificati, o anche Tcontro ?. Parte del problema è che quando Andronico raggiunge il punto vitale della sua spiegazione, passa a un altro thread che utilizza solo esempi banali. Speravo di ottenere una risposta più chiara e completa qui.
jaco0646,

1
@ jaco0646 OK. Il documento "Domande frequenti su Java Generics - Argomenti di tipo" di Angelika Langer ha una FAQ intitolata Cosa significano i caratteri jolly multilivello (ovvero nidificati)? . Questa è la migliore fonte che conosco per spiegare le questioni sollevate nella domanda del PO. Le regole per i caratteri jolly nidificati non sono né semplici né intuitive.
skomisa,

1

Ti consiglierei di consultare la documentazione dei caratteri jolly generici, in particolare le linee guida per l'uso dei caratteri jolly

Francamente parlando del tuo metodo #doesntCompile

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

e chiama come

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

È fondamentalmente errato

Aggiungiamo l' implementazione legale :

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

Va davvero bene, perché Double estende il numero, quindi put List<Double>è assolutamente perfettoList<Integer> , giusto?

Tuttavia, pensi ancora che sia legale passare qui new HashMap<Integer, List<Integer>>()dal tuo esempio?

Il compilatore non la pensa così e sta facendo del suo meglio per evitare tali situazioni.

Prova a fare la stessa implementazione con il metodo #compile e il compilatore ovviamente non ti permetterà di mettere un elenco di doppi nella mappa.

    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
    }

Fondamentalmente non puoi mettere nulla, ma List<T>è per questo che è sicuro chiamare quel metodo con new HashMap<Integer, List<Integer>>()o new HashMap<Integer, List<Double>>()o new HashMap<Integer, List<Long>>()o new HashMap<Integer, List<Number>>().

Quindi, in poche parole, stai cercando di imbrogliare con il compilatore e si difende abbastanza da tale imbroglio.

NB: la risposta inviata da Maurice Perry è assolutamente corretta. Non sono sicuro che sia abbastanza chiaro, quindi ho provato (spero davvero di esserci riuscito) di aggiungere post più ampi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.