CodeGym /Corsi /JAVA 25 SELF /Collezioni immutabili: Collections.unmodifiable

Collezioni immutabili: Collections.unmodifiable

JAVA 25 SELF
Livello 33, Lezione 2
Disponibile

1. Il problema della mutabilità delle collezioni

In Java le collezioni ricordano un magazzino di merce: chiunque può venire e aggiungere, togliere, cambiare qualcosa. A volte è comodo, ma nelle applicazioni grandi diventa un mal di testa. Immaginate di aver passato una lista di prodotti dal vostro class all’esterno, e qualcuno ha preso e rimosso metà delle voci. Oppure — ancora più divertente — in un programma multithread un thread aggiunge elementi e un altro li legge: il risultato può essere inatteso e gli errori difficili da intercettare (per esempio, ConcurrentModificationException).

Ecco un esempio del perché la mutabilità delle collezioni è fonte di bug:

import java.util.*;

public class Inventory {
    private List<String> products = new ArrayList<>();

    public Inventory() {
        products.add("Tè");
        products.add("Caffè");
    }

    public List<String> getProducts() {
        // PERICOLOSO! Restituiamo un riferimento alla lista interna
        return products;
    }
}
public class Main {
    public static void main(String[] args) {
        Inventory inv = new Inventory();
        List<String> external = inv.getProducts();
        external.remove("Tè"); // Ops! Ora nell'inventario non c'è più tè
        System.out.println(inv.getProducts()); // [Caffè]
    }
}

Avete notato la trappola? Un metodo restituisce la collezione interna, un altro la modifica. Così si possono distruggere per errore dati che dovrebbero essere protetti.

2. Creare collezioni immutabili: Collections.unmodifiable*

Per evitare simili inconvenienti, Java offre una protezione efficace: si può rendere una collezione «immutabile» con speciali wrapper della classe Collections:

  • Collections.unmodifiableList(list)
  • Collections.unmodifiableSet(set)
  • Collections.unmodifiableMap(map)

Come funziona? Prima create una normale collezione, poi la avvolgete in un guscio «immutabile»:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> drinks = new ArrayList<>();
        drinks.add("Tè");
        drinks.add("Caffè");

        List<String> immutableDrinks = Collections.unmodifiableList(drinks);

        System.out.println(immutableDrinks); // [Tè, Caffè]

        // Proviamo ad aggiungere un elemento
        immutableDrinks.add("Cacao"); // Boom! UnsupportedOperationException
    }
}

Il tentativo di modificare tale collezione provoca un’eccezione UnsupportedOperationException. È come attaccare sulla scatola un enorme adesivo "NON TOCCARE!" — e chiunque provi ad aggiungere o rimuovere qualcosa, si farà male alle mani (o allo stack delle chiamate).

Esempio: proteggiamo lo stato interno

Correggiamo la nostra classe Inventory dall’esempio precedente:

import java.util.*;

public class Inventory {
    private List<String> products = new ArrayList<>();

    public Inventory() {
        products.add("Tè");
        products.add("Caffè");
    }

    public List<String> getProducts() {
        // Ora restituiamo il wrapper
        return Collections.unmodifiableList(products);
    }
}

Ora, se qualcuno prova a modificare la lista ottenuta, riceverà un’eccezione.

3. Comportamento delle collezioni immutabili: protezione superficiale

È importante capire: unmodifiableList e i suoi «fratelli» fanno solo un wrapper attorno alla collezione di origine. Non creano una copia — qualsiasi modifica della collezione originale (quella «interna») sarà visibile anche nel wrapper!

Dimostrazione

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> drinks = new ArrayList<>();
        drinks.add("Tè");
        List<String> immutableDrinks = Collections.unmodifiableList(drinks);

        drinks.add("Caffè"); // Modifichiamo la collezione originale
        System.out.println(immutableDrinks); // [Tè, Caffè] — l'elemento è comparso!
    }
}

Conclusione: il wrapper protegge solo dalle modifiche attraverso il wrapper stesso. Se qualcuno detiene un riferimento alla collezione originale, può comunque modificarla.

4. Immutabilità profonda: miti e realtà

I wrapper unmodifiable* rendono la collezione immutabile solo dall’esterno. Ma se la collezione contiene oggetti mutabili, questi possono essere modificati!

Esempio

import java.util.*;

class Product {
    String name;
    Product(String name) {
        this.name = name;
    }
    public String toString() {
        return name;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Product> products = new ArrayList<>();
        products.add(new Product("Tè"));
        List<Product> immutableProducts = Collections.unmodifiableList(products);

        // Modifichiamo l'oggetto dentro la collezione
        immutableProducts.get(0).name = "Caffè";
        System.out.println(immutableProducts); // [Caffè]
    }
}

Conclusione:

  • La collezione è «immutabile», ma gli oggetti al suo interno no.
  • Per una vera (profonda) immutabilità usate oggetti immutabili (per esempio, String, Integer, classi record o rendete immutabili le vostre classi).

5. Quando usare le collezioni immutabili

Per proteggere lo stato interno

Se scrivete una classe che conserva una collezione e la esponete all’esterno, restituite sempre un wrapper affinché nessuno possa modificare per errore (o di proposito) i vostri dati:

public List<String> getProducts() {
    return Collections.unmodifiableList(products);
}

Nelle applicazioni multithread

Nelle applicazioni multithread le collezioni mutabili sono fonte di problemi (race condition, ConcurrentModificationException e altre «gioie della vita»). Se la collezione non va modificata dopo la creazione, rendetela immutabile.

Per passare dati tra layer

Se passate una collezione da un layer dell’applicazione a un altro (per esempio, dal DAO al service), passate una copia immutabile o un wrapper — protegge da modifiche accidentali.

6. Esempi pratici

Esempio 1: proteggiamo l’elenco degli studenti

import java.util.*;

public class Group {
    private final List<String> students = new ArrayList<>();

    public void addStudent(String name) {
        students.add(name);
    }

    public List<String> getStudents() {
        return Collections.unmodifiableList(students);
    }
}

Ora nessuno potrà aggiungere o rimuovere studenti direttamente tramite getStudents().

Esempio 2: mappa immutabile (Map)

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> grades = new HashMap<>();
        grades.put("Vasya", 5);
        grades.put("Masha", 4);

        Map<String, Integer> immutableGrades = Collections.unmodifiableMap(grades);

        // immutableGrades.put("Petya", 3); // UnsupportedOperationException
    }
}

7. Dettagli utili

Alternative moderne: List.of, Set.of, Map.of

In Java 9 sono apparsi modi ancora più comodi per creare collezioni immutabili:

List<String> drinks = List.of("Tè", "Caffè");
Set<String> fruits = Set.of("Mela", "Banana");
Map<String, Integer> ages = Map.of("Vasya", 20, "Masha", 21);
  • Queste collezioni sono immutabili (qualsiasi tentativo di modificarle — eccezione).
  • Non hanno una collezione «originale» mutabile (a differenza di Collections.unmodifiable*).
  • Non ammettono il valore null.

Inoltre, da Java 10 ci sono i metodi di copia: List.copyOf, Set.copyOf, Map.copyOf — creano una copia immutabile della collezione passata.

Confronto dei modi per creare collezioni immutabili

Metodo Immutabilità profonda Si può modificare la collezione originale? Ammette null? Versione di Java
Collections.unmodifiableList(list)
No Sì Sì 1.2
List.of(...), Set.of(...), Map.of(...)
No No (nessuna collezione originale) No 9+

8. Errori tipici nell’uso delle collezioni immutabili

Errore n. 1: modificare la collezione originale dopo aver creato il wrapper. Avete creato unmodifiableList, e poi qualcuno modifica la lista originale. Il wrapper non vi proteggerà da questo — le modifiche saranno visibili ovunque venga usato il wrapper.

Errore n. 2: aspettarsi l’immutabilità profonda. Molti pensano che se la collezione è immutabile, anche gli oggetti al suo interno non possano essere modificati. In realtà è protetta solo la struttura (aggiunta/rimozione/modifica tramite la collezione), non il contenuto degli oggetti.

Errore n. 3: usare collezioni immutabili con il valore null nelle factory moderne. Le collezioni create tramite List.of, Set.of, Map.of non ammettono null. Tentare di aggiungere o ottenere null porterà a un’eccezione.

Errore n. 4: passare riferimenti a collezioni mutabili all’esterno. Se restituite all’esterno un riferimento alla collezione interna (senza wrapper), perdete il controllo sui vostri dati — strada diretta verso bug e violazione degli invarianti.

Errore n. 5: usare collezioni immutabili in codice che si aspetta mutabilità. Se codice di terze parti prova a modificare la collezione (per esempio, aggiungere un elemento), otterrà UnsupportedOperationException. Assicuratevi che i consumatori sappiano dell’immutabilità dei dati.

1
Compito
JAVA 25 SELF, livello 33, lezione 2
Bloccato
Creazione di un elenco protetto di libri rari nella biblioteca 📚
Creazione di un elenco protetto di libri rari nella biblioteca 📚
1
Compito
JAVA 25 SELF, livello 33, lezione 2
Bloccato
Menu dinamico nel ristorante: come le modifiche alle ricette influenzano la versione per gli ospiti 🍽️
Menu dinamico nel ristorante: come le modifiche alle ricette influenzano la versione per gli ospiti 🍽️
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION