CodeGym /Corsi /JAVA 25 SELF /Generics: perché servono, sintassi di base

Generics: perché servono, sintassi di base

JAVA 25 SELF
Livello 26 , Lezione 4
Disponibile

1. Il problema delle collezioni «raw» (raw types)

Un po’ di storia. Fino a Java 5 tutte le collezioni erano «onnivore». Memorizzavano oggetti di tipo Object e il compilatore non controllava cosa ci stavi mettendo dentro. Vuoi inserire una stringa? Prego. Un numero? Perché no. Un gatto? Si può anche quello.

// Esempio di collezioni "raw" (raw types), Java prima della versione 5
List list = new ArrayList();
list.add("Ciao");
list.add(42);
list.add(new Object());

Il problema emergeva quando «tiravi fuori» e usavi il valore:

String s = (String) list.get(0); // OK, è una stringa
String s2 = (String) list.get(1); // BOOM! ClassCastException

Il compilatore tace, ma a runtime ottieni una ClassCastException. È come una scatola con l’etichetta «mele» in cui ci sono una tazza, una banana e un riccio.

Perché è un problema?

  • Gli errori emergono solo a runtime.
  • I tipi si confondono: devi effettuare manualmente i cast agli oggetti.
  • Il codice è meno leggibile e più rischioso.

La soluzione — generics (tipi generici)

I generics sono un meccanismo che consente di creare classi, interfacce e metodi con parametri di tipo. In pratica dici alla collezione: «Conserva solo stringhe», e il compilatore lo fa rispettare rigorosamente.

List<String> words = new ArrayList<>();
words.add("Ciao");
words.add("Mondo");
// words.add(42); // Errore di compilazione! Non si può aggiungere un int a List<String>

Ora il compilatore non permetterà di inserire nella lista nulla che non sia una stringa. L’errore viene intercettato prima dell’esecuzione del programma.

Idea chiave dei generics:
Garantire la sicurezza di tipo delle collezioni (e non solo), così da intercettare gli errori in fase di compilazione e non a runtime.

2. Sintassi dei generics: come appare nel codice

Specificare il tipo tra parentesi angolari

Quando crei una collezione, specifica il tipo degli elementi tra <>:

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// names.add(123); // Errore: non si può aggiungere un numero in una lista di stringhe

String first = names.get(0); // Nessun cast necessario!

Classici:

  • List<String> — elenco di stringhe
  • List<Integer> — elenco di interi
  • Set<Double> — insieme di numeri in virgola mobile
  • Map<String, Integer> — chiave String, valore Integer

Perché non scrivere semplicemente List?

Si può, ma perdi tutti i vantaggi dei generics, e il compilatore ti avviserà:

List list = new ArrayList(); // raw type — sconsigliato!
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // Ciao, ClassCastException!

Il codice Java moderno usa sempre i generics.

Operatore diamond <>

Da Java 7 non è necessario indicare il tipo a destra, se è chiaro dal contesto:

List<String> list = new ArrayList<>(); // Il compilatore capirà da solo che qui è <String>

3. Generics per diverse collezioni

Esempi per List, Set, Map

List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);

Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");

Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 23);
ages.put("Bob", 31);

Esempio con una classe propria

class Student {
    String name;
    int age;
    // ...
}

List<Student> students = new ArrayList<>();
students.add(new Student());

4. Sfumature utili

Vantaggi dei generics

Sicurezza di tipo. Il compilatore garantisce che nella collezione entrino solo elementi del tipo previsto.

Niente cast. Prima: String s = (String) list.get(0);. Ora: String s = list.get(0);.

Codice più leggibile e affidabile. Meno sorprese a runtime.

Limitazioni dei generics

Non puoi usare i tipi primitivi. I generics funzionano solo con oggetti, non con primitivi (int, double, boolean). Usa le classi wrapper: Integer, Double, Boolean.

List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int viene automaticamente convertito in Integer (autoboxing)

Brevemente sull’erasure dei tipi (type erasure)

In Java i generics sono implementati tramite il meccanismo di type erasure: dopo la compilazione l’informazione sui parametri di tipo viene eliminata, e a runtime la JVM non sa che si trattava di un List<String> e non semplicemente di un List. Questo è stato fatto per mantenere la retrocompatibilità.

Conseguenza: non si può verificare un parametro di tipo con instanceof usando un argomento di tipo concreto.

List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // Errore di compilazione!

Tentare di aggiungere un elemento di altro tipo — errore di compilazione

List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // Errore di compilazione: incompatible types: int cannot be converted to String

Map<String, Integer> map = new HashMap<>();
map.put("Gatto", 5);
// map.put(3, "Elefante"); // Errore: la chiave deve essere String, il valore — Integer

Ed è fantastico: gli errori vengono intercettati in fase di compilazione.

Non solo collezioni

I generics possono essere usati in classi e metodi propri. Ad esempio, una «Scatola» universale:

class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

Box<String> stringBox = new Box<>();
stringBox.set("Ciao");
System.out.println(stringBox.get());

Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());

Nelle collezioni i generics sono lo standard, ma li incontrerai anche altrove, ad esempio nello Stream API e in Optional.

5. Errori tipici nell’uso dei generics

Errore n. 1: uso di collezioni «raw». Una dichiarazione come List list = new ArrayList(); ti fa perdere la sicurezza di tipo. Indica sempre i parametri di tipo, ad esempio List<String>.

Errore n. 2: tentare di usare i primitivi. Non puoi scrivere List<int>, usa List<Integer>.

Errore n. 3: cast manuale quando leggi dalla collezione. Se usi i generics, il cast del tipo (String) list.get(i) non serve. Se ti tocca farlo, hai violato i tipi da qualche parte.

Errore n. 4: pensare che i parametri di tipo siano disponibili a runtime. A causa del type erasure non puoi verificarli con instanceof alla maniera di List<String>.

Errore n. 5: mescolare tipi diversi nella stessa collezione. Se è dichiarato List<String>, non provare ad aggiungere un Integer — il compilatore non lo permetterà, ed è un bene.

1
Compito
JAVA 25 SELF, livello 26, lezione 4
Bloccato
Catalogo della mia biblioteca digitale 📚✍️
Catalogo della mia biblioteca digitale 📚✍️
1
Compito
JAVA 25 SELF, livello 26, lezione 4
Bloccato
Bauli magici universali 📦✨
Bauli magici universali 📦✨
1
Sondaggio/quiz
Collezioni e generics, livello 26, lezione 4
Non disponibile
Collezioni e generics
Collezioni e generics
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION