CodeGym /Corsi /JAVA 25 SELF /Wildcard dei generics

Wildcard dei generics

JAVA 25 SELF
Livello 27 , Lezione 4
Disponibile

1. Invarianza dei generics: List<Number> ≠ List<Integer>

In Java i generics sono rigorosi: sono invarianti. Ciò significa che anche se un tipo è un sottotipo di un altro (per esempio, Integer è un sottotipo di Number), le collezioni con questi tipi non sono in alcun modo correlate tra loro.

Immaginate: avete una scatola di mele (List<Integer>), e qualcuno dice che è una «scatola di frutta» (List<Number>). Sembra logico: le mele sono frutta. Ma allora in quella scatola si potrebbe mettere una banana (Double), e tutto si romperebbe — in realtà la scatola è «per mele».

Esempio di codice:

List<Integer> intList = new ArrayList<>();
List<Number> numList = intList; // Errore di compilazione!

Qui il compilatore è volutamente rigido: non permette di trasformare una lista di «mele» in una lista di «frutta». Altrimenti potremmo aggiungere qualsiasi cosa e il programma andrebbe in errore in esecuzione.

Pertanto List<Number> e List<Integer> sono due tipi completamente diversi, nonostante Integer di per sé sia un sottotipo di Number.

A differenza degli array:

Integer[] intArr = new Integer[10];
Number[] numArr = intArr; // Consentito (gli array sono covarianti)
numArr[0] = 3.14; // Errore a runtime (ArrayStoreException)

Conclusione: i generics sono invarianti per garantire la sicurezza dei tipi in fase di compilazione.

2. Limiti dei parametri di tipo

A volte è necessario limitare con quali tipi può lavorare una classe o un metodo generico. A tale scopo si usano i limiti (bounds).

Limite superiore (extends)

class Stats<T extends Number> {
    private T[] nums;
    // ...
}

Ora Stats può lavorare solo con tipi che estendono Number (Integer, Double, Float, ecc.). Il tentativo di creare Stats<String> causerà un errore di compilazione.

Vincolo con interfaccia

class Sorter<T extends Comparable<T>> {
    void sort(List<T> list) { /* ... */ }
}

Ora Sorter può lavorare solo con tipi che implementano l’interfaccia Comparable.

Limiti multipli

È possibile specificare più vincoli separati da &:

class MyClass<T extends Number & Comparable<T>> { /* ... */ }

T deve estendere Number e implementare Comparable<T>.

L’ordine è importante: prima la classe, poi le interfacce.

3. Introduzione ai wildcard: ?

Il wildcard è un tipo «segnaposto» che dice: «Qui può esserci qualche tipo, ma non so esattamente quale».

Esempi:

  • List<?> — elenco di qualsiasi cosa.
  • List<? extends Number> — elenco di un qualunque tipo che estende Number (ad esempio, Integer, Double).
  • List<? super Integer> — elenco di un qualunque tipo che è un supertipo di Integer (ad esempio, Integer, Number, Object).

Perché servono i wildcard?

Permettono di scrivere metodi che lavorano con collezioni di tipi diversi ma correlati.

Esempio: sola lettura (producer)

void printNumbers(List<? extends Number> list) {
    for (Number n : list) {
        System.out.println(n);
    }
    // list.add(123); // Errore! Non è possibile aggiungere elementi
}
  • Si possono leggere gli elementi come Number.
  • Non si possono aggiungere elementi (tranne null).

Esempio: sola scrittura (consumer)

void addIntegers(List<? super Integer> list) {
    list.add(42); // OK
    // Integer x = list.get(0); // Errore! Non sappiamo quale tipo verrà restituito
}
  • Si possono aggiungere elementi di tipo Integer (o dei suoi sottotipi).
  • Non è sicuro leggere gli elementi come Integer (solo come Object).

Regola PECS

PECS — «Producer Extends, Consumer Super»:

  • Producer — Extends: se la collezione produce solo dati (producer), usa ? extends T.
  • Consumer — Super: se la collezione consuma solo dati (consumer), usa ? super T.

Facile da ricordare: extends — per leggere (producer), super — per scrivere (consumer).

Confronto: gli array sono covarianti, i generics invarianti

  • Gli array sono covarianti: Integer[] può essere assegnato a una variabile di tipo Number[].
  • I generics sono invarianti: List<Integer> non può essere assegnato a una variabile di tipo List<Number>.

I wildcard consentono di «ammorbidire» parzialmente l’invarianza dei generics.

5. Metodi generici e inferenza dei tipi; limitazioni (type erasure)

Metodi generici

Si possono dichiarare metodi generici che funzionano con qualsiasi tipo:

public static <T> void printList(List<T> list) {
    for (T elem : list) {
        System.out.println(elem);
    }
}

Inferenza del tipo (type inference)

Java può «intuire» il tipo del parametro:

List<String> strings = List.of("a", "b");
printList(strings); // T = String

Limitazioni dei generics: type erasure

  • In Java i generics sono implementati tramite type erasure: le informazioni sui tipi dei parametri vengono rimosse dopo la compilazione.
  • Nel bytecode non c’è differenza tra List<String> e List<Integer>.
  • Non si possono creare array di tipi generici: new List<String>[10] — errore.
  • Non si può usare instanceof con parametri di tipo: obj instanceof List<String> — errore.

6. Pratica con le collezioni e Stream API

Copia degli elementi tra liste

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (T item : src) {
        dest.add(item);
    }
}
  • src — producer (? extends T)
  • dest — consumer (? super T)

Utilizzo:

List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = new ArrayList<>();
copy(nums, ints); // OK: Number è un supertipo di Integer

Stream API e wildcard

List<Integer> ints = List.of(1, 2, 3);
List<? extends Number> numbers = ints;

numbers.stream()
    .map(Number::doubleValue)
    .forEach(System.out::println);

Filtraggio con wildcard

public static void printAll(List<?> list) {
    for (Object o : list) {
        System.out.println(o);
    }
}

7. Errori comuni

Errore n. 1: raw types (tipi grezzi).
L’uso dei «raw» types disattiva il controllo dei tipi e porta a errori a runtime.

List list = new ArrayList(); // raw type — male!
list.add("stringa");
list.add(123); // Si può aggiungere di tutto

String s = (String) list.get(1); // ClassCastException!

Non usare mai i raw types. Specifica sempre i parametri di tipo: List<String>, List<Integer>.

Errore n. 2: conversioni non sicure con extends.
Tentare di aggiungere elementi a una collezione con ? extends ... porta a un errore di compilazione.

List<? extends Number> nums = new ArrayList<Integer>();
nums.add(3.14); // Errore di compilazione!

Errore n. 3: «cancellazione» del tipo con l’overload.
Non si possono sovraccaricare metodi solo in base ai parametri generici — dopo il type erasure le firme coincidono.

public void process(List<String> list) { /* ... */ }
public void process(List<Integer> list) { /* ... */ } // Errore di compilazione!

Errore n. 4: array di tipi generici.
Non si possono creare array di tipi parametrizzati a causa del type erasure.

List<String>[] arr = new List<String>[10]; // Errore di compilazione!
1
Sondaggio/quiz
Interfacce delle collezioni, livello 27, lezione 4
Non disponibile
Interfacce delle collezioni
Interfacce delle collezioni
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION