CodeGym /Corsi /JAVA 25 SELF /Externalizable: controllo fine della serializzazione

Externalizable: controllo fine della serializzazione

JAVA 25 SELF
Livello 43 , Lezione 2
Disponibile

1. Introduzione

In Java per serializzare gli oggetti si usa più spesso l’interfaccia Serializable. È semplice: basta implementare l’interfaccia e l’oggetto si può scrivere/leggere tramite ObjectOutputStream/ObjectInputStream. Ma a volte non è sufficiente:

  • Serve controllare completamente quali campi vengono serializzati e come.
  • È necessario garantire la compatibilità tra diverse versioni della classe.
  • È importante ridurre la dimensione del file serializzato o velocizzare il processo.

Per questi casi in Java esiste l’interfaccia Externalizable — un approccio più “manuale” e flessibile alla serializzazione.

In breve:

  • Serializable — serializzazione automatica: è Java a decidere cosa e come scrivere.
  • Externalizable — serializzazione manuale: sei tu a indicare cosa e come salvare/ripristinare.

2. Contratto Externalizable: implementiamo writeExternal e readExternal

Per usare Externalizable, occorre:

  1. Implementare l’interfaccia java.io.Externalizable.
  2. Implementare obbligatoriamente due metodi:
    • void writeExternal(ObjectOutput out) throws IOException
    • void readExternal(ObjectInput in) throws IOException, ClassNotFoundException

Esempio:

import java.io.*;

public class User implements Externalizable {
    private String name;
    private int age;

    // Costruttore pubblico senza argomenti obbligatorio!
    public User() {}

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = in.readUTF();
        age = in.readInt();
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

Importante: lo sviluppatore decide in autonomia quali campi serializzare e in quale ordine. C’è però un requisito obbligatorio: la classe deve avere un costruttore public senza parametri. Se manca, durante la deserializzazione il programma genererà InvalidClassException.

3. Quando usare Externalizable?

Usa Externalizable se:

  • Hai bisogno di un controllo completo sul formato dei dati. Ad esempio, vuoi serializzare solo una parte dei campi oppure serializzarli in un ordine/formato particolare.
  • Ottimizzazione di prestazioni e dimensione del file. La serializzazione standard aggiunge informazioni di servizio (metadati, nomi delle classi, tipi, ecc.). Con Externalizable scrivi solo i dati necessari.
  • Garanzia di retrocompatibilità. Se la struttura della classe cambia, puoi implementare manualmente la logica di lettura delle versioni vecchie e nuove dei dati.
  • Serializzazione di oggetti non standard. Per esempio, se hai campi che non si possono serializzare con il metodo standard (ad esempio transient, volatile o strutture complesse).

Quando NON conviene usarlo?

  • Se non ti serve un controllo completo — usa Serializable, è più semplice e sicuro.
  • Se non sei sicuro di poter mantenere la compatibilità del formato dei dati quando la classe cambia.

4. Pro e contro di Externalizable rispetto a Serializable

Vantaggi:

  • Controllo completo sulla serializzazione. Decidi tu cosa e come scrivere/leggere.
  • Compattezza. Niente metadati superflui — solo i tuoi dati.
  • Velocità. Meno dati — scrittura/lettura più rapida.
  • Flessibilità. Puoi implementare il supporto di diverse versioni del formato, aggiungere compressione, cifratura, ecc.

Svantaggi:

  • Implementazione manuale — è facile sbagliare. Se inverti l’ordine di scrittura/lettura, la serializzazione “si rompe” (errore o dati non corretti).
  • Nessun supporto automatico per transient, serialVersionUID. Va pensato e implementato tutto a mano.
  • Manutenzione più complessa. Se cambia la struttura della classe, non dimenticare di aggiornare i metodi di serializzazione.
  • Costruttore pubblico senza parametri obbligatorio.
  • Meno “magia” — più responsabilità.

5. Esempi: serializzazione e deserializzazione di un oggetto semplice

Serializzazione dell’oggetto

User user = new User("Alice", 30);

try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
    out.writeObject(user);
}

Deserializzazione dell’oggetto

try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
    User loaded = (User) in.readObject();
    System.out.println(loaded); // Alice (30)
}

Attenzione: se cambi l’ordine di scrittura/lettura dei campi o dimentichi di serializzare un campo, i dati saranno incorretti! I metodi writeExternal e readExternal devono essere strettamente allineati nella sequenza delle operazioni.

Esempio: serializziamo solo una parte dei campi

public class SecretUser implements Externalizable {
    private String login;
    private transient String password; // transient non ha rilevanza per Externalizable

    public SecretUser() {}

    public SecretUser(String login, String password) {
        this.login = login;
        this.password = password;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(login);
        // Non serializziamo la password!
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        login = in.readUTF();
        password = null; // la password non viene ripristinata
    }
}

6. Pratica: confronto della dimensione del file serializzato

Confrontiamo quanto “pesano” i file serializzati con Serializable e con Externalizable.

Classe con Serializable

public class UserSerializable implements Serializable {
    private String name;
    private int age;

    public UserSerializable(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Classe con Externalizable

public class UserExternalizable implements Externalizable {
    private String name;
    private int age;

    public UserExternalizable() {}

    public UserExternalizable(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = in.readUTF();
        age = in.readInt();
    }
}

Codice per il confronto

import java.io.*;

public class CompareSerialization {
    public static void main(String[] args) throws Exception {
        UserSerializable s = new UserSerializable("Bob", 25);
        UserExternalizable e = new UserExternalizable("Bob", 25);

        // Serializable
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"))) {
            out.writeObject(s);
        }

        // Externalizable
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ext.bin"))) {
            out.writeObject(e);
        }

        System.out.println("Serializable file size: " + new File("ser.bin").length());
        System.out.println("Externalizable file size: " + new File("ext.bin").length());
    }
}

Risultato:
Il file ser.bin (Serializable) di solito è più grande — contiene informazioni di servizio di Java. Il file ext.bin (Externalizable) — solo i tuoi dati, di norma è più piccolo.

7. Errori tipici nell’uso di Externalizable

Errore n. 1: assenza di un costruttore pubblico senza argomenti.
La classe che implementa Externalizable deve avere obbligatoriamente un costruttore public senza argomenti. In caso contrario, la deserializzazione genererà InvalidClassException.

Errore n. 2: violazione dell’ordine di scrittura e lettura dei campi.
I metodi writeExternal e readExternal devono operare nello stesso ordine. Se in scrittura salvi prima il campo name e in lettura provi prima a leggere age, i dati risulteranno corrotti.

Errore n. 3: campi omessi durante la serializzazione.
Se dimentichi di scrivere un campo in writeExternal, in deserializzazione avrà valore null (per i tipi reference) o 0 (per i numerici).

Errore n. 4: uso scorretto di transient o serialVersionUID.
A differenza di Serializable, per Externalizable questi meccanismi non funzionano automaticamente — devi controllare tu quali campi salvare e quali no.

Errore n. 5: modifica della struttura della classe senza aggiornare i metodi.
Se aggiungi o rimuovi campi e non apporti i cambiamenti corrispondenti a writeExternal e readExternal, i dati salvati in passato potrebbero non caricarsi correttamente.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION