CodeGym /Corsi /JAVA 25 SELF /Introduzione alla serializzazione degli oggetti: a cosa s...

Introduzione alla serializzazione degli oggetti: a cosa serve

JAVA 25 SELF
Livello 42 , Lezione 0
Disponibile

1. Perché serve la serializzazione

Immaginate che il vostro oggetto sia come le cose che portate con voi in vacanza. La serializzazione è l’impacchettamento di tutto il contenuto della valigia in un contenitore speciale che si può mettere in stiva o spedire per posta. La deserializzazione, di conseguenza, è lo spacchettamento di questo contenitore e l’ottenimento degli oggetti nella forma originaria.

In sostanza, la serializzazione converte un oggetto in un flusso di byte che può essere salvato su file, inviato in rete o semplicemente mantenuto in memoria. La deserializzazione fa l’inverso: ricostruisce l’oggetto da questo flusso. Per dirla in modo molto semplice, la serializzazione è come «congelare» un oggetto per poi «scongelarlo» e riottenerlo nello stesso stato.

Salvataggio dello stato degli oggetti tra le esecuzioni del programma

Uno degli scenari più comuni è il salvataggio dello stato dell’applicazione. Ad esempio, avete un elenco di utenti, i risultati di un gioco o le impostazioni dell’app. Tutto ciò è comodo da conservare direttamente sotto forma di oggetti. Per non perdere i dati tra le esecuzioni, si serializzano su file e, al successivo avvio, si deserializzano.

Un buon esempio è il salvataggio in un gioco. Quando il giocatore completa un livello, il suo progresso viene «congelato» e scritto su file tramite la serializzazione. Il giorno dopo avvia il gioco e il progresso viene «scongelato»: i dati dal file tornano a essere oggetti, e il giocatore continua dallo stesso punto in cui si era fermato.

Creiamo un semplice salvataggio:

import java.io.*;

// La classe del giocatore deve essere Serializable
class Player implements Serializable {
    String name;
    int score;

    Player(String name, int score) {
        this.name = name;
        this.score = score;
    }
}

public class GameSaveExample {
    public static void main(String[] args) throws Exception {
        // Creiamo un oggetto giocatore
        Player player = new Player("Ihor", 1500);

        // --- Salvataggio (serializzazione) ---
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("save.dat"))) {
            out.writeObject(player);
            System.out.println("Progresso salvato!");
        }

        // --- Caricamento (deserializzazione) ---
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("save.dat"))) {
            Player loaded = (Player) in.readObject();
            System.out.println("Progresso caricato: " + loaded.name + " con punteggio " + loaded.score);
        }
    }
}

Attenzione: perché questo codice funzioni, la classe Player deve implementare l’interfaccia Serializable. Maggiori dettagli su di essa nella prossima lezione!

  • Player è una classe normale con i campi nome e punteggio, marcata con l’interfaccia Serializable (implements Serializable).
  • ObjectOutputStream scrive l’oggetto nel file "save.dat".
  • ObjectInputStream legge lo stesso oggetto indietro.
  • Di conseguenza otteniamo un vero salvataggio: al prossimo avvio il programma caricherà l’oggetto del giocatore con lo stesso stato.

Invio di oggetti tramite rete e tra JVM

Nei sistemi distribuiti spesso è necessario trasferire oggetti tra programmi diversi o persino tra macchine diverse. Ad esempio, avete un client e un server che devono scambiarsi messaggi. La serializzazione consente di «impacchettare» un oggetto da un lato, inviarlo in rete e «spacchettarlo» dall’altro.

Esempio: il client invia al server un oggetto d’ordine (Order), il server lo riceve, lo deserializza e lo elabora.

Uso nelle tecnologie Java

  • RMI (Remote Method Invocation): consente di invocare metodi di oggetti remoti — la serializzazione è necessaria per passare argomenti e valori di ritorno.
  • Sessioni HTTP: nei servlet, gli oggetti in sessione vengono serializzati durante il riavvio del container.
  • JMS (Java Message Service): i messaggi tra componenti possono essere serializzati.
  • Caching: gli oggetti possono essere serializzati per la memorizzazione nella cache (su disco o in un archivio distribuito).

Caching e portabilità

Se volete salvare rapidamente risultati intermedi (ad esempio per il caching), la serializzazione è uno strumento eccellente. Serializzate un oggetto, lo salvate su disco o in memoria e poi lo ripristinate velocemente senza ricalcoli.

2. Esempi di scenari d’uso della serializzazione

Salvataggio di una collezione di utenti su file

Supponiamo di avere una classe User:

public class User {
    String name;
    int age;
    // ... altri campi
}

E avete un elenco di utenti:

List<User> users = new ArrayList<>();
users.add(new User("Vasya", 25));
users.add(new User("Masha", 30));
// ... e così via

Per salvare questo elenco su file lo serializzate. Quando necessario, lo deserializzate e ottenete la stessa lista con gli stessi utenti. Ricordate che la classe User (e tutti i suoi campi) deve supportare la serializzazione, cioè implementare Serializable.

Invio di un messaggio tra client e server

Un esempio classico è una chat. L’utente scrive un messaggio, l’oggetto Message viene serializzato e inviato in rete. Il server riceve il flusso di byte, deserializza l’oggetto, lo elabora e, eventualmente, lo inoltra.

import java.io.*;
import java.net.*;

// Il messaggio deve essere Serializable
class Message implements Serializable {
    String text;

    Message(String text) {
        this.text = text;
    }
}

// Server
class Server {
    public static void main(String[] args) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(5000)) {
            System.out.println("Il server è in attesa di una connessione...");
            Socket socket = serverSocket.accept();
            System.out.println("Client connesso!");

            try (ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
                Message msg = (Message) in.readObject();
                System.out.println("Messaggio ricevuto: " + msg.text);
            }
        }
    }
}

// Client
class Client {
    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket("localhost", 5000)) {
            try (ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream())) {
                Message msg = new Message("Ciao, server!");
                out.writeObject(msg);
                System.out.println("Messaggio inviato!");
            }
        }
    }
}

Come funziona:

  1. Per prima cosa si avvia Server (rimane in attesa di una connessione).
  2. Poi si avvia Client (si connette a "localhost:5000").
  3. Il client serializza l’oggetto Message e lo invia tramite il socket.
  4. Il server riceve il flusso di byte, lo deserializza e stampa il testo.

Qui usiamo i socket (ServerSocket, Socket) — un meccanismo di comunicazione di rete che studierete più avanti. Al momento non contano i dettagli della rete, ma l’idea: il client crea un oggetto Message, lo serializza e lo invia; il server riceve il flusso di byte, lo deserializza di nuovo in un oggetto e stampa il messaggio. Così, anche se non è ancora chiaro cosa siano le classi ServerSocket e Socket, l’esempio mostra il valore della serializzazione: grazie ad essa si può «impacchettare» un oggetto, trasferirlo in rete e, dall’altro lato, spacchettarlo senza conversioni superflue.

Caching degli oggetti

Nelle applicazioni di grandi dimensioni si usa spesso il caching per accelerare l’esecuzione. Ad esempio, i risultati di calcoli complessi vengono serializzati e salvati nella cache (file, database, archivio distribuito). Al successivo accesso il risultato si può ripristinare velocemente deserializzando l’oggetto.

import java.io.*;

// Il risultato dei calcoli che vogliamo mettere in cache
class Result implements Serializable {
    int value;

    Result(int value) {
        this.value = value;
    }
}

public class CacheExample {
    private static final String CACHE_FILE = "cache.dat";

    public static void main(String[] args) throws Exception {
        Result result;

        // Verifichiamo se esiste una cache
        File file = new File(CACHE_FILE);
        if (file.exists()) {
            // Carichiamo il risultato dalla cache
            try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
                result = (Result) in.readObject();
                System.out.println("Caricato dalla cache: " + result.value);
            }
        } else {
            // Calcolo "pesante" (per l'esempio, solo il quadrato di un numero)
            int x = 12345;
            System.out.println("Calcolo in corso... (è lento)");
            result = new Result(x * x);

            // Salviamo il risultato nella cache
            try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
                out.writeObject(result);
                System.out.println("Salvato nella cache: " + result.value);
            }
        }
    }
}

3. Limitazioni e rischi della serializzazione

La serializzazione è uno strumento potente, ma non privo di insidie. Vediamo le principali limitazioni e i rischi.

Non tutti gli oggetti possono essere serializzati

In Java non tutti gli oggetti possono essere serializzati «out of the box». Ad esempio, gli oggetti collegati a risorse esterne (file, connessioni di rete, stream di I/O) non sono serializzabili. È logico: non si può serializzare un «file aperto» o una connessione di rete «viva» — il loro stato dipende dal sistema operativo e dall’ambiente di esecuzione.

Esempio: una classe con un campo di tipo FileInputStream non può essere serializzata: tentando di serializzarla si verificherà un errore.

Questioni di sicurezza

La serializzazione può rappresentare una potenziale falla di sicurezza. Se deserializzate dati provenienti da una fonte non attendibile (ad esempio da Internet), un attaccante potrebbe inviare un flusso di byte malevolo che porti a comportamenti inattesi del vostro programma e, talvolta, persino all’esecuzione di codice dannoso.

Regola: Non deserializzate mai dati da fonti non affidabili! È come accettare un pacco da un mittente sconosciuto: dentro potrebbe esserci di tutto.

Compatibilità tra versioni

Se modificate la struttura di una classe (ad esempio aggiungete o rimuovete un campo), gli oggetti serializzati in precedenza potrebbero diventare incompatibili con la nuova versione della classe. Questo può portare a errori in fase di deserializzazione. Analizzeremo questo aspetto in dettaglio nelle lezioni successive.

Prestazioni

La serializzazione binaria in Java è abbastanza veloce, ma talvolta non è la più compatta e non sempre è comoda per lo scambio con altri linguaggi di programmazione. Per l’interoperabilità con sistemi esterni si usano spesso formati testuali (JSON, XML).

4. Errori tipici al primo approccio alla serializzazione

Errore n. 1: tentare di serializzare un oggetto che non implementa l’interfaccia Serializable.
Di conseguenza otterrete l’eccezione NotSerializableException. Non dimenticate di indicare esplicitamente implements Serializable nella classe e di assicurarvi che anche tutti i campi siano serializzabili!

Errore n. 2: serializzare oggetti con campi non serializzabili.
Se la vostra classe contiene un campo di un tipo che non supporta la serializzazione (ad esempio uno stream o una connessione al DB), la serializzazione non funzionerà. Soluzione: contrassegnare tali campi come transient (ne parleremo più avanti).

Errore n. 3: deserializzare dati da fonti non affidabili.
Questo può portare a vulnerabilità di sicurezza o persino all’esecuzione di codice malevolo. Fidatevi solo dei dati che sono stati serializzati dalla vostra applicazione!

Errore n. 4: modificare la struttura della classe dopo la serializzazione.
Se avete salvato un oggetto e poi avete aggiunto o rimosso un campo nella classe, al tentativo di deserializzazione si verificherà un errore o compariranno valori «strani». Maggiori dettagli nelle lezioni successive.

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