List<Integer> li = new ArrayList<>();
List<Object> lo;
Se li
fosse un sottotipo di lo
l'assegnamento seguente sarebbe possibile (senza cast)
lo = (List)li;
Una volta ottenuto l'alias lo
potremmo usarlo per aggiungere di tutto a li
lo.add(new Object());
ma ovviamente, una volta estratto da li
(come Integer
) un oggetto qualunque potrebbe dare adito ad un errore di conversione
li.get(0);
Abbiamo già visto che il punto, in questo caso, è poter aggiungere e togliere elementi del tipo parametrico (avendo sia la garnzia della type safety che evitando l'uso del cast).
li.add(Integer.valueOf(1));
Integer i = li.get(1);
Inoltre, come già discusso, vale invece che se G
$\prec$ H
, allora per ogni T
vale che G<T>
$\prec$ H<T>
, di nuovo usando una immagine dal tutorial

Array e gerarchia¶
Si osservi che la situazione è ben diversa per gli array: è infatti vero che se S
$\prec$ T
, allora S[]
$\prec$ T[]
.
Integer[] ai = new Integer[10];
Object[] ao;
In questo caso, infatti, l'assegmaneto è possibile senza cast (per via della relazione di sottotipo)
ao = ai;
Il fatto è che, per gli array, l'assegnamento a ao
può essere controllato a run-time perché l'array conserva l'informazione circa il tipo dei suoi elementi (cosa che non accade per i tipi generici).
ao[0] = new Object();
L'eccezione ArrayStoreException
è proprio il modo in cui la VM segnala (a runtime) che è impossibile assegnare un oggetto ad un elemento di as
(tramite l'alias ao
).
Ovviamente è corretto l'assegnamento
ao[0] = Integer.valueOf(1);
che viene eseguito senza errore.
Nel caso degli array quindi la type safety può essee garantita anche "trasportando" sugli array la relazione di sottotipo dgli elementi.
Metodi generici¶
La mancanza di relazioni di tipo tra G<S>
e G<T>
anche qualora S
$\prec$ T
è particolarmente grave non tanto per il caso degli alias visto in precendeza, quanto per il caso dei metodi.
Supponiamo di voler scrivere il metodo add
al quale passeremo un parametro che "produca" dei valori numerici di cui il metodo restiturà la somma
public static double add(List<Number> lst) {
double sum = 0;
for (Number n : lst) sum += n.doubleValue();
return sum;
}
Tutto bene se lo usiamo con una lista del tipo del parametro
List<Number> nums = List.of(1, 2.5, 3);
add(nums)
Ma per via della mancanza della relazione di sottotipo, non possiamo usarla per sommare una lista di interi
List<Integer> ints = List.of(1, 2, 3);
add(ints);
Una possibile soluzione è rendere il metodo generico ed indicare un bound nella dichiarazione dei parametri di tipo
public static <T extends Number> double add(List<T> lst) {
double sum = 0;
for (T n : lst) sum += n.doubleValue();
return sum;
}
add(ints);
Supponiamo ora di voler scrivere un metodo copy
che data una lista proceda a copiare il suo contenuto in un "consumatore" costituito da una seconda lista
public static <T> void copy(List<T> src, List<T> dst) {
dst.clear();
for (T t : src) dst.add(t);
}
che funziona egregiamente su coppie di liste di interi
List<Integer> dupInts = new ArrayList<>();
copy(ints, dupInts);
dupInts
Ma che succede se volessimo copiare una lista di interi in una di numeri?
List<Number> dupNums = new ArrayList<>();
copy(ints, dupNums);
Incorreremmo di nuovo in un problema legato all'assenza di una relazione gerarchica tra i tipi; anche in questo caso, possiamo risolverlo con un bound
public static <T, S extends T> void copy(List<S> src, List<T> dst) {
dst.clear();
for (S t : src) dst.add(t);
}
copy(ints, dupNums);
dupNums
L'uso dei bound sui tipi di parametro dei metodi generici è una prima risposta al problema della mancanza di una gerarchia tra i generici.
Ma è necessaria una soluzione più versatile, in grado di permetterci di dare un tipo (generico) sensato agli argomenti dei metodi.
List<?> lw;
lw = lo;
lw = li;
Il problema è che in questo modo non è possibile garantire alcuna type safety, infatti non c'è verso di scrivere nella lista
lw.add(Integer.valueOf(1));
lw.add(new Object());
Anche la lettura non può essere fatta in modo type safe
Integer i = lw.get(1);
Ma se l'obiettivo è solo recuperare un elemento, si può fare a patto di usare Object
come tipo d'ultima istanza
Object o = lw.get(0);
Tale "libertà" può essere vincolata in vario modo, così che abbia senso usare tipi parametrici basati su wildcard.
Upper bound¶
Il caso più semplice è quello degli upper bound della forma ? extends T
che consentono di introdurre le seguenti relazione di sottotipo (fissato G
):
- per ogni
T
,G<T>
$\prec$G<? extends T>
- se
S
$\prec$T
, alloraG<? extends S>
$\prec$G<? extends T>
.
Ragionando per transitività, se S
$\prec$ T
, si ha G<S>
$\prec$ G<? extends T>
che, in somma, permette di concludere che il generico basato sull'upper bound del supertipo (G<? extends T>
) è supertipo sia di G<S>
che di G<T>
(che pure sono tra loro inconfrontabili dal punto di vista della gerarchia).
Esempio: produttore¶
Un esempio d'uso può chiarire l'obiettivo di tali bound. Immaginiamo di avere un metodo in grado di operare su oggetti di tipo T
prodotti da una lista; esso potrà riceverne una di tipo List<T>
ma, certamente, anche List<S>
(se S
$\prec$ T
); per questa ragione ha senso che il tipo del sua parametro sia List<? extends T>
.
Ad esempio, consideriamo il metodo add
visto in precedenza: a questo punto ha senso abbia un parametro di tipo List<? extends Number>
che è supertipo di List<Integer>
e List<Double>
.
static double add(List<? extends Number> lst) {
double sum = 0;
for (Number n : lst) sum += n.doubleValue();
return sum;
}
add(ints);
add(nums);
Il parametro del metodo add
viene chiamato produttore perché produce i valori adoperati dal metodo, se esso è in grado di gestire il tipo Number
sarà in grado di gestire i sottotipi. Il produttore, quindi, deve emettere elementi al più di un certo tipo, per questa ragione il suo parametro ha un upper bound.
Lower bound¶
Immaginiamo ora di avere un metodo in grado di consumare oggetti di tipo T
da immagazzinare in una lista, che tipo dovrebbe avere quest'ultima? Non possiamo seguire il ragionamento precedente: volendo aggiungere valori di tipo T
non possiamo farlo in una lista di tipo List<? extends T>
, perché sappiamo che se S
$\prec$ T
ad essa può corrispondere anche una List<S>
e finiremmo col mettere oggetti del supertipo in una lista di sottotipi! Vorremmo scrivere una cosa del tipo T extends ?
, ma questo non è sintatticamente ammesso.
A tal fine vengono invece introdotti i lower bound della forma ? super S
che consentono di introdurre le seguenti relazioni di sottotipo
- per ogni
T
,G<T>
$\prec$G<? super T>
- se
S
$\prec$T
, alloraG<? super T>
$\prec$G<? super S>
si osservi che, nella seconda relazione, l'ordine dei generici è rovesciato rispetto a prima.
Ragionando per transitività, se S
$\prec$ T
, si ha G<T>
$\prec$ G<? super S>
che, in somma, permette di concludere che: il generico basato sul lower bound del sottotipo (G<? super S>
) è supertipo sia di G<S>
che di G<T>
.
Esempio: consumatore¶
Ad esempio, consideriamo di nuovo il metodo copy
visto in precedenza. L'unico vincolo in questo caso è che il consumatore di elementi (estratti dalla lista di T
) sia una List<? super T>
(che è supertipo di List<T>
) in grado di ricevere elementi di tipo T
.
static <T> void copy(List<T> src, List<? super T> dst) {
dst.clear();
for (T t : src) dst.add(t);
}
copy(ints, dupInts);
dupInts
copy(ints, dupNums);
dupNums
Il secondo parametro del metodo copy
viene chiamato consumatore perché riceve o valori dalla (prima) lista.
Si osservi inoltre che con le wildcard è possibile anche restringere il tipo della prima lista, ad esempio ad Integer
, in questo modo non è nemmeno necessario che il metodo sia generico
static void copy(List<Integer> src, List<? super Integer> dst) {
dst.clear();
for (Integer t : src) dst.add(t);
}
copy(ints, dupInts);
dupInts
copy(ints, dupNums);
dupNums
Schemi riassuntivi¶
Alcune visualizzazioni possono essere comode per ricordare le relazioni introdotte in questa nota. La prima riprende esattamente le relazioni espresse nella precedente sezione

Una bella immagine di Andrey Tyukin può aiutare a riflettere sulle relazioni tra tipo e le nozioni di produttore e consumatore.

Per finire, una immagine tratta dal tutorial può aiutare a ricordare l'ordine indotto da queste relazioni
