Originariamente inviato da Hysoka
è proprio questo quello che non riesco a capire...a cosa servono questi wildcards?
I wildcard sono stati introdotti per offrire una specie di "polimorfismo" anche sui tipi parametrici.

I generics sono stati implementati tramite una tecnica che si chiama "erasure" e per via di questo è stato deciso di rendere i tipi parametrici "invarianti". Un esempio per chiarire: un ArrayList<Number> non è un ArrayList<Object>!! (nonostante Number è un Object).

codice:
ArrayList<Number> numArr = new ArrayList<Number>();
ArrayList<Object> objArr = numArr;    // NOOO, errore
Se questa cosa fosse permessa, non avendo informazioni sul tipo parametrico a runtime ("erasure"), prova a immaginare cosa potrebbe succedere....

Il fatto che i tipi parametrici sono invarianti ovviamente è stato visto come un limite. Ecco quindi che sono stati introdotti i wildcard (che hanno regole molto particolari e complesse).

codice:
ArrayList<Number> numArr = new ArrayList<Number>();
ArrayList<? extends Object> objArr = numArr;    // SI
A questo punto potresti chiederti: posso inserire un String usando objArr?? La risposta è no! Il wildcard indica: "non so di che tipo è". Con l'extends che dice: "ma sicuramente è di un tipo Object o suo sottotipo".
Se non si sa di che tipo è, chiaramente non si può inserire nulla.

Ora potresti chiederti a cosa serve un wildcard. Bene, l'esempio pratico e lampante è nella interfaccia Collection<E> che ha un metodo:

boolean addAll(Collection<? extends E> c)

Se hai una Collection<Number>, puoi aggiungere alla collezione tutti gli elementi in una Collection<Number> ma anche Collection<Integer> o Collection<Double>. Perché è logico. E con il wildcard è possibile.

(piccola nota: complicando un pochino la dichiarazione di addAll si poteva evitare l'uso del wildcard ma con il wildcard è decisamente più chiaro e semplice).