Quando si parla di serializzazione in Java, si fa riferimento al processo che permette di trasformare un oggetto in una sequenza di byte per salvarlo o trasmetterlo. La deserializzazione consente di ricostruirlo a partire da quei byte.
Di norma, la serializzazione include tutti i campi di istanza di una classe, tranne quelli dichiarati come static o marcati con il modificatore transient. Quest’ultimo serve a escludere esplicitamente un campo dal processo, ad esempio per motivi di sicurezza o perché non è serializzabile (come un oggetto Thread).
Tuttavia, Java offre anche un approccio alternativo e più restrittivo: il meccanismo basato sulla variabile serialPersistentFields. Questo sistema consente di dichiarare in modo esplicito quali campi devono essere serializzati, ignorando tutti gli altri. È una strategia di tipo white list, che sovrascrive completamente la logica predefinita di esclusione (black list) basata su transient.
public class Persona implements java.io.Serializable {
private String nome, cognome;
private transient Thread t = new Thread();
private transient String codiceSegreto;
private static final ObjectStreamField[] serialPersistentFields
= {new ObjectStreamField("nome", String.class),
new ObjectStreamField("codiceSegreto", String.class)};
public Persona(String nome, String cognome, String cs) {
this.setNome(nome);
this.setCognome(cognome);
this.setCodiceSegreto(cs);
}
// Metodi setter e getter omessi
public String toString() {
return "Nome: " + getNome() + "\nCognome: " + getCognome() +
"\nCodice Segreto: " + getCodiceSegreto();
}
}
Nel codice sopra, solo i campi nome e codiceSegreto vengono inclusi nella serializzazione, mentre cognome — pur non essendo dichiarato transient — viene ignorato. Questo perché la variabile serialPersistentFields sovrascrive completamente la logica predefinita del meccanismo basato su transient. In altre parole, quando si usa la white list, solo i campi esplicitamente indicati vengono serializzati, tutti gli altri vengono esclusi.
Possiamo verificare facilmente il comportamento del meccanismo con un semplice esempio di serializzazione e deserializzazione con le seguenti classi:
import java.io.*;
public class SerializeObject {
public static void main(String[] args) throws IOException {
Persona p = new Persona("Claudio", "De Sio Cesari", "xxx");
try (FileOutputStream f = new FileOutputStream("persona.ser");
ObjectOutputStream s = new ObjectOutputStream(f)) {
s.writeObject(p);
System.out.println("Oggetto serializzato!");
}
}
}
import java.io.*;
public class DeSerializeObject {
public static void main(String[] args) throws Exception {
Persona p = null;
try (FileInputStream f = new FileInputStream("persona.ser");
ObjectInputStream s = new ObjectInputStream(f)) {
p = (Persona) s.readObject();
System.out.println("Oggetto deserializzato!");
System.out.println(p);
}
}
}
Eseguendo il programma di deserializzazione otteniamo il seguente output:
Oggetto deserializzato! Nome: Claudio Cognome: null Codice Segreto: xxx
Possiamo notare che il campo cognome risulta null: non è stato incluso nel flusso di byte, perché non fa parte della white list definita da serialPersistentFields.
Questo approccio può risultare utile in contesti in cui è necessario un controllo rigoroso o centralizzato dei dati che devono essere salvati o trasmessi, come in applicazioni sensibili o versionabili. Tuttavia, nella maggior parte dei casi, l’uso di transient è più chiaro e leggibile, poiché mostra direttamente nel codice quali campi non devono essere serializzati.
Per approfondire la serializzazione e altri aspetti avanzati del linguaggio Java, ti invito a dare un’occhiata al mio libro Programmazione Java.
