CodeGym /Corsi /JAVA 25 SELF /campi transient, serialVersionUID

campi transient, serialVersionUID

JAVA 25 SELF
Livello 43 , Lezione 1
Disponibile

1. Più in dettaglio su transient

In Java la parola chiave transient — è un modo per dire al serializer: «Per favore, non toccare questo campo, dimenticalo durante il salvataggio dell'oggetto!». Se dichiari un campo come transient, non finirà nello stream di byte serializzato. È particolarmente utile per dati sensibili (ad esempio password) o per calcoli temporanei che non devono essere salvati.

Esempio: a cosa serve transient?

Supponiamo di avere una classe utente:

import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private transient String password; // Non vogliamo salvare la password!

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    // Qui abbiamo getter e setter
}

Se serializziamo un oggetto di questa classe, il campo password non finirà nel file (o in un altro stream). Ciò significa che durante la deserializzazione la password avrà il valore predefinito — per gli oggetti è null, per i numeri — 0, per booleanfalse.

Come funziona nella pratica?

Facciamo un mini esperimento. Per prima cosa serializziamo l'utente:

import java.io.*;

public class TransientDemo {
    public static void main(String[] args) throws Exception {
        User user = new User("vasya", "qwerty123");

        // Salviamo l'oggetto in un file
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.ser"));
        out.writeObject(user);
        out.close();

        // Ora leggiamo l'oggetto di nuovo
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.ser"));
        User restored = (User) in.readObject();
        in.close();

        System.out.println("Username: " + restored.username);
        System.out.println("Password: " + restored.password);
    }
}

Risultato:

Username: vasya
Password: null

Come vedi, il campo password non è stato ripristinato — è transient, quindi il serializer l'ha ignorato.

Dove e perché usare transient?

  • Password e token. Non serializzarli mai!
  • Dati in cache o temporanei. Ad esempio, se hai un campo che può essere calcolato «al volo».
  • Oggetti che non si possono o non è necessario serializzare. Ad esempio, riferimenti a connessioni DB, stream, socket.

Particolarità del comportamento dei campi transient

Quando un oggetto viene deserializzato, tutti i campi contrassegnati come transient ricevono i valori predefiniti. Se occorre ridare loro significato, si può usare il metodo readObject e popolarli manualmente (ricalcolare una cache, chiedere la password all'utente ecc.).

2. serialVersionUID: identificatore univoco della versione della classe

serialVersionUID è un campo statico speciale di tipo long che definisce la «versione» della classe serializzabile. Durante la serializzazione viene scritto il valore di serialVersionUID, durante la deserializzazione la JVM lo confronta con il valore nella classe corrente. Se non coincidono — verrà lanciata un'eccezione e l'oggetto non verrà ripristinato.

Come dichiarare serialVersionUID?

Molto semplice:

private static final long serialVersionUID = 1L;

Di solito lo si dichiara direttamente nella classe che implementa Serializable:

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // ... altri campi e metodi
}

A cosa serve serialVersionUID?

Immagina di aver salvato un oggetto della classe su un file e poi di aver modificato la struttura della classe (hai aggiunto un campo, rinominato qualcosa ecc.). Se serialVersionUID differisce, la JVM considera la classe incompatibile con la versione precedente e non ti consentirà di deserializzare l'oggetto. Questo previene errori inattesi.

Cosa succede se non dichiari serialVersionUID?

Se non dichiari esplicitamente serialVersionUID, il compilatore lo genererà automaticamente — in base alla struttura della classe. Ma anche una piccola modifica (ad esempio, aggiungere o rimuovere un campo) porterà a un cambiamento di serialVersionUID. Di conseguenza non potrai deserializzare oggetti salvati con la vecchia versione della classe.

Pertanto si consiglia di impostare sempre esplicitamente serialVersionUID!

Dimostrazione: mancata corrispondenza di serialVersionUID

1) Per prima cosa creiamo la classe e serializziamo un oggetto:

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;

    public User(String username) {
        this.username = username;
    }
}

2) Poi cambiamo serialVersionUID:

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 2L; // Era 1L, è diventato 2L!
    private String username;

    public User(String username) {
        this.username = username;
    }
}

Risultato:

java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

La JVM avverte chiaramente: «Le versioni sono incompatibili!»

Quale valore scegliere per serialVersionUID?

Molto spesso si usano valori semplici (1L, 2L, 42L), mentre nei progetti grandi l'IDE genera valori «lunghi». L'importante è cambiarlo solo quando la struttura della classe cambia in modo non compatibile.

3. Pratica: transient-campi e serialVersionUID in azione

Esempio: classe con un campo transient

Modifichiamo un'applicazione didattica (ad esempio, un gestore di contatti) e aggiungiamo alla classe utente un campo per memorizzare un token di autorizzazione temporaneo che non deve finire nella serializzazione.

import java.io.Serializable;

public class Contact implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private String phone;
    private transient String sessionToken; // token temporaneo

    public Contact(String name, String phone, String sessionToken) {
        this.name = name;
        this.phone = phone;
        this.sessionToken = sessionToken;
    }

    @Override
    public String toString() {
        return "Contact{" +
               "name='" + name + '\'' +
               ", phone='" + phone + '\'' +
               ", sessionToken='" + sessionToken + '\'' +
               '}';
    }
}

Ora proviamo a serializzare e deserializzare l'oggetto:

import java.io.*;

public class TransientAndSUIDDemo {
    public static void main(String[] args) throws Exception {
        Contact c = new Contact("Ivan", "+19990001122", "token-12345");

        // Salviamo l'oggetto
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("contact.ser"));
        out.writeObject(c);
        out.close();

        // Ripristiniamo l'oggetto
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("contact.ser"));
        Contact restored = (Contact) in.readObject();
        in.close();

        System.out.println("Prima della serializzazione: " + c);
        System.out.println("Dopo la deserializzazione: " + restored);
    }
}

Output:

Prima della serializzazione: Contact{name='Ivan', phone='+19990001122', sessionToken='token-12345'}
Dopo la deserializzazione: Contact{name='Ivan', phone='+19990001122', sessionToken='null'}

Come vedi, il campo sessionToken non è stato ripristinato — è transient.

Esempio: esperimento con serialVersionUID

1) Per prima cosa serializziamo un oggetto con serialVersionUID = 1L.
2) Poi cambiamo serialVersionUID a 2L e proviamo a deserializzare lo stesso file.

Risultato: otterrai un'InvalidClassException, come mostrato sopra.

4. Perché è meglio impostare esplicitamente serialVersionUID?

  • L'esplicito è meglio dell'implicito. Controlli la compatibilità: se la struttura della classe non è cambiata in modo critico, lasci il vecchio serialVersionUID e gli oggetti si deserializzeranno senza problemi.
  • La generazione automatica è rischiosa. Qualsiasi modifica può cambiare il valore calcolato e «rompere» la compatibilità dei dati salvati.
  • L'IDE ti aiuta. La maggior parte delle IDE (ad esempio, IntelliJ IDEA) sono in grado di generare automaticamente serialVersionUID.

5. Errori tipici nell'uso di transient e serialVersionUID

Errore n. 1: hai dimenticato di contrassegnare un campo sensibile come transient.
Di conseguenza, password o token finiscono accidentalmente nei file serializzati. Non è solo imbarazzante, ma anche pericoloso.

Errore n. 2: non hai dichiarato esplicitamente serialVersionUID.
La classe è stata modificata e ora non è possibile deserializzare i vecchi oggetti: la JVM li considera incompatibili, anche se in sostanza la struttura potrebbe non essere cambiata in modo critico.

Errore n. 3: hai cambiato serialVersionUID senza necessità.
Se hai solo aggiunto un getter o un commento, non è necessario cambiare serialVersionUID — altrimenti i vecchi dati smetteranno di deserializzarsi.

Errore n. 4: serialVersionUID non è static o non è final.
Il campo deve essere dichiarato come private static final long serialVersionUID. Altrimenti la JVM non lo interpreterà correttamente.

Errore n. 5: hai dimenticato di ripristinare il campo transient dopo la deserializzazione.
Se il valore è critico per il funzionamento dell'oggetto, ripristinalo in readObject — altrimenti l'oggetto potrebbe non funzionare correttamente.

1
Compito
JAVA 25 SELF, livello 43, lezione 1
Bloccato
Marcatura temporale per i dati personali: versionamento dell'entità
Marcatura temporale per i dati personali: versionamento dell'entità
1
Compito
JAVA 25 SELF, livello 43, lezione 1
Bloccato
I Segreti dello Stipendio: Scomparsa Temporanea e Ripristino Predefinito
I Segreti dello Stipendio: Scomparsa Temporanea e Ripristino Predefinito
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION