Pagina 2 di 2 primaprima 1 2
Visualizzazione dei risultati da 11 a 18 su 18

Discussione: Parametri covarianti

Hybrid View

  1. #1
    Utente di HTML.it
    Registrato dal
    Oct 2014
    Messaggi
    315
    beh nums1 è un ArrayList che potrà contenere SOLO elementi di tipi sottoclasse di Number (es. Integer, Double, Float, ecc.), nums2 invece è sempre un ArrayList ma potrà contenere elementi di tipi superclasse di Integer (es. Integer, Number o Object). Giusto?

  2. #2
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    Quote Originariamente inviata da newutente Visualizza il messaggio
    nums1 è un ArrayList che potrà contenere SOLO elementi di tipi sottoclasse di Number (es. Integer, Double, Float, ecc.)
    Nooo!

    Quote Originariamente inviata da newutente Visualizza il messaggio
    nums2 invece è sempre un ArrayList ma potrà contenere elementi di tipi superclasse di Integer (es. Integer, Number o Object). Giusto?
    Noo, neanche!

    E proprio qui che volevo (farti) arrivare.

    Una lista che può contenere qualunque tipo di oggetto che "è-un" Number (Integer, Double ecc... anche in modo promiscuo) è List<Number>.
    List<? extends Number> è un'altra cosa! Vuol dire una lista la cui istanziazione concreta della parametrizzazione non è nota a priori ma dal bound si "sa" solo che ha come limite superiore Number.
    Quindi le seguenti assegnazioni sono tutte lecite:

    List<? extends Number> nums1 = new ArrayList<Integer>();

    List<? extends Number> nums1 = new ArrayList<Double>();

    List<? extends Number> nums1 = new ArrayList<Long>();

    Ma solo "guardando" la variabile nums1 io NON so quale è la reale parametrizzazione. Se non so quale è ... questo ha una conseguenza importantissima: non posso aggiungere nulla nella lista!

    nums1.add(new Integer(123)); // No
    nums1.add(new Long(123)); // No
    ecc...

    Perché? Se la parametrizzazione fosse <Integer> posso aggiungere un Long o Double? Ovviamente no, violeresti la invarianza dei generics (dovuta alla erasure) e renderesti nulli i benefici dei generics.
    Ma se non la sai la parametrizzazione .... allora proprio per questo non puoi passare qualcosa come argomenti di cui non sai la tipizzazione.
    L'unica cosa che puoi legalmente passare (a add ad esempio) è un null letterale.

    nums1.add(null);

    Perché? Semplicemente perché null è il sottotipo di tutti i tipi reference! Quindi è anche il sottotipo di ? extends Number.

    Quello che puoi fare, ed è per questo che lo si usa, è estrarre un valore e solo vedendolo con il tipo del bound (o super-tipo):

    Number n1 = nums1.get(2);
    oppure
    Object n1 = nums1.get(2); // ovvio, Number è-un Object

    ---------------------------

    List<? super Integer> invece è al contrario. Non vuol dire che può contenere qualunque cosa di tipo Integer o super-tipo .... vuol dire anche qui che la istanziazione concreta della parametrizzazione non è nota a priori ma dal bound si sa solo che sicuramente ha come limite inferiore Integer.

    Quindi questi sono tutti leciti:

    List<? super Integer> nums2 = new ArrayList<Integer>();

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

    List<? super Integer> nums2 = new ArrayList<Object>();

    Posso estrarre qualcosa? Sì ma solo in senso "degenerato" come Object.

    Object o = nums2.get(2);

    Non posso "vedere" altro. Non potrei vedere es. Number .... e se la parametrizzazione reale fosse <Object>? Non avrebbe senso (potrebbe contenere dei String o Date!).
    E se invece fosse <Integer>? Qui in effetti sembrerebbe strano ... dopotutto Integer è-un Number, perché non posso estrarre come Number? Perché a livello di bytecode qualcosa di concreto lo deve sostituire come tipizzazione effettiva. E usa Object.

    codice:
    List<? super Integer> nums2 = new ArrayList<Integer>();
    Number n = nums2.get(0);

    Nel JDK8 il messaggio di errore è un po' più contorto/incomprensibile, quindi ti mostro cosa dice il JDK5:

    [nome sorgente:riga]: incompatible types
    found : java.lang.Object
    required: java.lang.Number
    Number n = nums2.get(0);

    Per il compilatore il tipo estraibile è Object ... che non è un Number.

    Posso inserire qualcosa in nums2? Sì certo! Ma solo qualcosa che è del tipo del bound o sottotipi. Nel caso sopra solo Integer (che è final, quindi nessun sottotipo).
    Perché qualunque sia la parametrizzazione reale (<Integer>, <Number>, <Object>), sono assolutamente certo che è uguale o superiore a Integer e quindi almeno un Integer sicuramente lo può contenere.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

  3. #3
    Utente di HTML.it
    Registrato dal
    Oct 2014
    Messaggi
    315
    Innanzitutto grazie per la spiegazione come sempre dettagliatissima.

    Quote Originariamente inviata da andbin Visualizza il messaggio
    List<? extends Number> è un'altra cosa! Vuol dire una lista la cui istanziazione concreta della parametrizzazione non è nota a priori ma dal bound si "sa" solo che ha come limite superiore Number.
    Quindi le seguenti assegnazioni sono tutte lecite:

    List<? extends Number> nums1 = new ArrayList<Integer>();

    List<? extends Number> nums1 = new ArrayList<Double>();

    List<? extends Number> nums1 = new ArrayList<Long>();

    Ma solo "guardando" la variabile nums1 io NON so quale è la reale parametrizzazione. Se non so quale è ... questo ha una conseguenza importantissima: non posso aggiungere nulla nella lista!

    nums1.add(new Integer(123)); // No
    nums1.add(new Long(123)); // No
    ecc...

    Perché? Se la parametrizzazione fosse <Integer> posso aggiungere un Long o Double? Ovviamente no, violeresti la invarianza dei generics (dovuta alla erasure) e renderesti nulli i benefici dei generics.
    Ma se non la sai la parametrizzazione .... allora proprio per questo non puoi passare qualcosa come argomenti di cui non sai la tipizzazione.
    L'unica cosa che puoi legalmente passare (a add ad esempio) è un null letterale.

    nums1.add(null);

    Perché? Semplicemente perché null è il sottotipo di tutti i tipi reference! Quindi è anche il sottotipo di ? extends Number.

    Quello che puoi fare, ed è per questo che lo si usa, è estrarre un valore e solo vedendolo con il tipo del bound (o super-tipo):

    Number n1 = nums1.get(2);
    oppure
    Object n1 = nums1.get(2); // ovvio, Number è-un Object
    il discorso mi è chiaro però volevo capire la differenza che c'è tra l'utilizzo delle wildcard bounded all'interno di una dichiarazione di una struttura (es. ArrayList) e l'utilizzo all'interno dei parametri passati ad un metodo.
    Abbiamo detto che bisogna necessariamente specificare, al momento dell'istanziazione della struttura, il tipo "corretto" e quindi il vantaggio delle istruzioni viste in precedenza è che è possibile sfruttare il polimorfismo, come è possibile allora che un metodo del genere
    codice:
    public void print(List <? extends Number> list) {
          for(Iterator<? extends Number> i = list.iterator(); i.hasNext(); ) {
                System.out.println(i.next());
          }
    }
    compila senza problemi? in quel caso io sto prendendo in input una lista dove non viene specificato il tipo "corretto" che può contenere.
    Ovviamente provo a dare io una risposta. Quel metodo è fatto in quella maniera perchè in quel modo posso invocarlo su qualsiasi tipo di struttura che contenga oggetti sottotipi di Number quindi sarà lecito scrivere:
    codice:
    List<? extends Number> lista = new ArrayList<Integer>;
    oggetto.print(lista);
    ma anche:
    codice:
    List<? extends Number> lista = new ArrayList<Double>;
    oggetto.print(lista);
    Giusto?

  4. #4
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    Quote Originariamente inviata da newutente Visualizza il messaggio
    quindi il vantaggio delle istruzioni viste in precedenza è che è possibile sfruttare il polimorfismo
    Sì, i "bound" nei generics sono stati introdotti proprio per fornire una "sorta" di polimorfismo anche sulle parametrizzazioni. Il "vero" polimorfismo è quello ovviamente dovuto alla ereditarietà tra classi, che ormai dovresti aver acquisito.
    Ma con i bound si ha una specie di polimorfismo che permette maggior libertà con i generics che, è bene ricordare, sono trattati solo a livello di sorgente/compilazione e sono invarianti (mentre un String[] è-un Object[], un List<String> NON è un List<Object>).

    Il modo di uso di extends/super con i wildcard è quello che il libro "Java Generics and Collections" chiama The Get and Put Principle. Con un extends puoi solo "estrarre" un valore (in pratica da un tipo di ritorno di un metodo) e con super puoi solo inserire un valore (un argomento di un metodo).
    Se devi estrarre E inserire, non va usato né extends né super.


    Quote Originariamente inviata da newutente Visualizza il messaggio
    come è possibile allora che un metodo del genere
    codice:
    public void print(List <? extends Number> list) {
          for(Iterator<? extends Number> i = list.iterator(); i.hasNext(); ) {
                System.out.println(i.next());
          }
    }
    compila senza problemi?
    Certo, compila senza problemi. Stai solo estraendo non inserendo (passando qualcosa in argomento).

    Quote Originariamente inviata da newutente Visualizza il messaggio
    codice:
    List<? extends Number> lista = new ArrayList<Integer>;
    oggetto.print(lista);
    ma anche:
    codice:
    List<? extends Number> lista = new ArrayList<Double>;
    oggetto.print(lista);
    Giusto?
    Tecnicamente corretto. Ma avere la variabile 'lista' con il bound in extends è poco utile .... non puoi inserire nulla! Quindi almeno la variabile mettila uguale alla parametrizzazione concreta (ovvero List<Integer>).


    Ti faccio un esempio davvero lampante con super. Immagina un metodo che vuole inserire un range di interi in una lista. Senza i bound si potrebbe fare:

    codice:
    public static void addIntRange(List<Integer> lista, int min, int max) {
        for (int n = min; n <= max; n++) {
            lista.add(n);   // autoboxing int -> Integer
        }
    }

    Il problema è che al metodo posso passare solo un (Xyz)List<Integer> ... non posso passare una lista es. di <Number> o <Object>. Che sarebbero sensati perché entrambi possono lecitamente contenere dei Integer.
    Se cambio il parametro e lo metto List<Object> lista il metodo continua a compilare/funzionare ma posso passare solo una lista <Object> ... non <Number> o <Integer> (invarianza!).

    Insomma è molto (troppo) rigido.

    Allora entra in gioco un bounded wildcard con super:

    codice:
    public static void addIntRange(List<? super Integer> lista, int min, int max) {
        for (int n = min; n <= max; n++) {
            lista.add(n);   // autoboxing int -> Integer
        }
    }

    Qui devo mettere come bound Integer. Visto che realmente sto passando dei Integer, è l'unico tipo che mi garantisce che qualunque sia la parametrizzazione concreta usata nel chiamante, è lecito inserire dei Integer.
    Ora posso passare List<Integer> o List<Number> o List<Object>. Ovviamente non es. List<String> o List<Date> sono fuori dal bound (e nemmeno in relazione con Integer) e non avrebbero senso (e il compilatore lo "sa").
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

  5. #5
    Utente di HTML.it
    Registrato dal
    Oct 2014
    Messaggi
    315
    ora è tutto molto più chiaro, grazie.

  6. #6
    Andrea grazie per la spiegazione. ora la cosa risulta più chiara come sempre preparatissimo chissà se riuscirò pure io ad essere così.. .
    mi riaggancio all'esempio iniziale...

    public class TestAnimali
    {
    public static void main(String args[])
    {
    Animale<Erbivoro> tigre = new Carnivoro<Erbivoro>();
    Erbivoro<Erba> erbivoro = new Erba<Erba>();
    tigre.mangia(erbivoro);
    }
    }
    Perchè
    Animale<Erbivoro> tigre = new Carnivoro<Erbivoro>();
    Erbivoro<Erba> erbivoro = new Erba<Erba>();
    tigre.mangia(erbivoro);
    Questa cosa mi mette confusione... Perchè tigre stà su animale<erbivoro> e poi = new Carnivoro<Erbivoro> ?

  7. #7
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    Quote Originariamente inviata da francesco51 Visualizza il messaggio
    Questa cosa mi mette confusione... Perchè tigre stà su animale<erbivoro> e poi = new Carnivoro<Erbivoro> ?
    Per i generics, hai visto che sono invarianti: un List<String> non è-un List<Object>. Ma il polimorfismo si applica al tipo base!
    Ovvero:

    ArrayList<String> è-un List<String> che è-un Collection<String> e anche un Iterable<String>

    Sul tipo base (ArrayList) il polimorfismo funziona, perché questa è la ereditarietà tra le classi/interfacce.


    Comunque ti rifaccio notare che se Carnivoro è

    public class Carnivoro implements Animale<Erbivoro>

    allora new Carnivoro<Erbivoro>() è sbagliato.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2026 vBulletin Solutions, Inc. All rights reserved.