CodeGym /Corsi /JAVA 25 SELF /Analisi degli errori tipici nella serializzazione delle c...

Analisi degli errori tipici nella serializzazione delle collezioni

JAVA 25 SELF
Livello 44 , Lezione 4
Disponibile

1. NotSerializableException: quando la collezione non vuole essere serializzata

L'errore più frequente e insidioso nella serializzazione delle collezioni è java.io.NotSerializableException. Si verifica se almeno un elemento della collezione non implementa l'interfaccia Serializable.

Vediamo un esempio ingenuo:

import java.io.*;
import java.util.*;

class Book {
    String title;
    Book(String title) { this.title = title; }
}

public class LibraryApp {
    public static void main(String[] args) throws Exception {
        List<Book> books = new ArrayList<>();
        books.add(new Book("Dombey e figlio"));

        // Tentativo di serializzare la collezione
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("books.ser"))) {
            oos.writeObject(books); // BOOM! NotSerializableException
        }
    }
}

Cosa succede? Al momento di oos.writeObject(books) otterrete un'eccezione:

java.io.NotSerializableException: Book

Perché? Perché la classe Book non implementa l'interfaccia Serializable. Anche se la collezione stessa (ArrayList) sa essere serializzata, gli elementi della collezione devono essere serializzabili!

Come diagnosticare

Nel messaggio di errore è sempre indicata la classe che ha causato il problema — cercatela nel testo dell'eccezione. Se la collezione è grande e l'errore si verifica solo in determinate condizioni, è possibile che un elemento sia stato aggiunto per sbaglio e non implementi Serializable.

Come risolvere

Aggiungete implements Serializable alla vostra classe:

class Book implements Serializable {
    String title;
    Book(String title) { this.title = title; }
}

Suggerimento: Se la collezione contiene tipi di oggetti diversi, verificate che tutti implementino Serializable!

2. ClassCastException durante la deserializzazione: quando i generics tradiscono

In Java le informazioni sui parametri generici delle collezioni vengono eliminate dopo la compilazione (type erasure). Ciò significa che se avete serializzato un List<String>, ma deserializzate come List<Integer>, il compilatore non rileverà l'errore, ma a runtime otterrete una ClassCastException.

Esempio:

// Serializzazione
List<String> names = Arrays.asList("Anna", "Boris");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("names.ser"))) {
    oos.writeObject(names);
}

// Deserializzazione (PERICOLOSO!)
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("names.ser"))) {
    List<Integer> numbers = (List<Integer>) ois.readObject(); // unchecked cast
    Integer first = numbers.get(0); // BOOM! ClassCastException
}

Errore:

java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer

Come evitare

  • Non usate collezioni "raw" (raw types) e non fate cast non necessari.
  • Verificate i tipi degli elementi dopo la deserializzazione se non siete sicuri del loro contenuto.
  • Documentate quale tipo di collezione viene serializzato e quale ci si aspetta in lettura.

Esempio di deserializzazione sicura:

Object obj = ois.readObject();
if (obj instanceof List<?>) {
    List<?> list = (List<?>) obj;
    if (!list.isEmpty() && list.get(0) instanceof String) {
        @SuppressWarnings("unchecked")
        List<String> safeNames = (List<String>) obj; // avviso soppresso, ma il tipo è stato verificato!
    }
}

3. Modifica della struttura delle classi: serialVersionUID e compatibilità all'indietro

Avete serializzato una collezione e poi avete deciso di aggiungere un nuovo campo alla classe degli elementi, cambiare il nome di un campo o addirittura modificare la struttura della classe. Ora, tentando di deserializzare il vecchio file, otterrete un errore enigmatico:

java.io.InvalidClassException: Book; local class incompatible: stream classdesc serialVersionUID = 1234, local class serialVersionUID = 5678

Perché accade

Ogni classe serializzabile ha un identificatore di versione univoco — serialVersionUID. Se la classe è cambiata (ad esempio avete aggiunto un campo), la JVM calcola un nuovo serialVersionUID e, in deserializzazione, si vede che la versione della classe non coincide con quella usata al momento della serializzazione.

Come evitare

  • Dichiarate esplicitamente serialVersionUID nelle vostre classi:
class Book implements Serializable {
    private static final long serialVersionUID = 1L;
    String title;
    // ...
}
  • Mantenete la compatibilità all'indietro: non eliminate e non rinominate campi se prevedete di leggere file più vecchi.
  • Testate la deserializzazione dopo le modifiche.

Cosa fare se è comunque necessario modificare la classe?

  • Valutate l'implementazione dei metodi readObject/writeObject per gestire manualmente la serializzazione.
  • Oppure migrate i dati: leggete il vecchio file con la versione precedente della classe, poi risalvatelo nel nuovo formato.

4. Perdita di dati nella serializzazione delle collezioni immutabili

Nelle versioni più recenti di Java sono state introdotte collezioni immutabili, ad esempio create tramite List.of(), Set.of(), Map.of(). Nelle versioni più vecchie di Java (fino alla 12) e in alcune implementazioni di terze parti la serializzazione di tali collezioni può non funzionare correttamente: dopo la deserializzazione la collezione può diventare mutabile oppure si verifica un errore.

Esempio:

List<String> list = List.of("a", "b", "c");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.ser"))) {
    oos.writeObject(list);
}

Su JVM più vecchie, in fase di deserializzazione si verificava un errore oppure la collezione non risultava più immutabile.

Come evitare

  • Controllate la documentazione della versione di Java in uso.
  • Testate la serializzazione e deserializzazione di tali collezioni.
  • Se dovete mantenere l'immutabilità, dopo la deserializzazione avvolgete la collezione con Collections.unmodifiableList(list).

5. Serializzazione dei campi transient e static

Cosa succede con questi campi:

  • transient — i campi contrassegnati con questa parola chiave non vengono serializzati. Dopo la deserializzazione avranno il valore predefinito (ad esempio, null o 0).
  • static — i campi di classe (e non dell'oggetto) non vengono mai serializzati.

Esempio:

class Book implements Serializable {
    String title;
    transient String cache; // non viene serializzato!
    static String publisher = "Default"; // non viene serializzato neanche questo!
}

Perché è importante

Se conservate valori calcolati o una cache all'interno dell'oggetto, contrassegnateli come transient: si risparmia spazio e si velocizza la serializzazione.

Attenzione: Dopo la deserializzazione, i campi transient devono essere ricalcolati o inizializzati di nuovo.

6. Serializzazione di collezioni grandi: prestazioni e dimensione del file

Problemi:

  • Le collezioni molto grandi (ad esempio, un milione di oggetti) possono produrre file enormi, tempi di scrittura e lettura molto lunghi e talvolta anche esaurimento della memoria (OutOfMemoryError).
  • Serializzando un grafo di oggetti (ad esempio collezioni complesse e interconnesse) la dimensione del file può crescere in modo inatteso.

Come evitare

  • Serializzate la collezione a pezzi: ad esempio, scrivete gli oggetti uno per volta o in piccoli lotti.
  • Usate l'elaborazione in streaming: invece di serializzare l'intera collezione in una volta, serializzate gli elementi man mano che servono.
  • Comprimete i file: utilizzate GZIPOutputStream per ridurre la dimensione del file.

Esempio di serializzazione in streaming:

try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("books.ser"))) {
    for (Book book : bigList) {
        oos.writeObject(book);
    }
}

Attenzione: Con questo approccio, per deserializzare è necessario sapere quanti oggetti sono stati scritti (oppure usare un apposito "marcatore di fine").

1
Compito
JAVA 25 SELF, livello 44, lezione 4
Bloccato
Interpretazione errata dei dati: `ClassCastException` dopo la deserializzazione 🚨
Interpretazione errata dei dati: `ClassCastException` dopo la deserializzazione 🚨
1
Compito
JAVA 25 SELF, livello 44, lezione 4
Bloccato
Archivista digitale: archiviazione efficiente di grandi volumi di dati con compressione 🗄️
Archivista digitale: archiviazione efficiente di grandi volumi di dati con compressione 🗄️
1
Sondaggio/quiz
Serializzazione di strutture complesse, livello 44, lezione 4
Non disponibile
Serializzazione di strutture complesse
Serializzazione di strutture complesse
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION