Il meccanismo della serializzazione consente di salvare oggetti Java all’interno di file (File di tipo ByteCode) oppure di trasmetterli in rete come informazioni a se stanti.
L’utilità di questo meccanismo è ovvia: poter salvare lo stato interno degli oggetti in un file per poterlo recuperare in un secondo momento, oppure poter trasmettere informazioni aggregate nella rete in un solo colpo senza doversi preoccupare di scinderle in informazioni primitive da trasmettere separatamente.
Cerchiamo di capire come può tornarci utile la serializzazione: supponiamo di avere una classe chiamata Record che rappresenta l’informazione di un dipendente. La struttura di questa classe, potrebbe essere la seguente:
public class Record {
private String nome;
private String cognome;
private Date dataNascita;
private int livello;
public Record(String nome, String cognome, Date dataNascita) {
this.nome = nome;
this.cognome = cognome;
this.dataNaschta = dataNascita;
livello = 0;
}
... // Altri metodi per recupero e
... // settaggio informazioni
}
Durante l’esecuzione del nostro programma potremmo avere una lista (un ArrayList, un Vector o quant’altro) che viene popolata mano a mano che vengono aggiunte le informazioni sui dipendenti. Questa lista, quindi, conterrà tante istanze della classe Record quanti sono i dipendenti che sono stati codificati. Sarebbe opportuno che, alla fine della giornata, quando l’applicazione viene chiusa non venissero perse tutte queste informazioni. Sarebbe ancora più bello non dover salvare queste informazioni all’interno di semplici file di testo (o file XML), che chiunque può andare liberamente a modificare.
Entra in gioco, quindi, la serializzazione: possiamo dire al nostro programma di andare a salvare in un file particolare, tutte le istanze della classe Record che sono state create. Questo file non sarà leggibile all’occhio umano, ma sarà codificato in ByteCode. Per fare questo, è sufficiente modificare leggermente l’intestazione della classe Record affinchè essa implementi l’interfaccia Serializable:
public class Record implements Serializable {
L’interfaccia Serializable, come si può vedere dalla documentazione, non prevede alcun metodo. E’, quindi, un’interfaccia vuota, che serve solo al compilatore per capire che quell’oggetto va trattato in modo particolare. Quello che viene fatto al bytecode della classe esula dagli scopi di questo articolo, ma in buona sostanza si può dire che viene aggiunto un campo nascosto chiamato “serialVersionUid“, che serve a contraddistinguere la versione della classe e a marcarne la serializzabilità.
Fatto questo, è possibile salvare ciascuna istanza della classe Record all’interno di un file. Per farlo è sufficiente costruire un ObjectOutputStream su di un file e salvarci dentro le istanze utilizzando il metodo writeObject(). Ecco un esempio, che salva tutti gli oggetti Record contenuti in un ipotetico Vector di dipendenti, chiamato appunto “dipendenti”:
public void salvaDipendenti() {
try {
// Apriamo il file
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("dipendenti.dat")
);
// Dopo vedremo il perché di questa linea
oos.writeObject( new Integer(dipendenti.size()) );
// Salviamo tutti i record
for(int i=0; i<dipendenti.size(); i++) {
oos.writeObject( (Record) dipendenti.elementAt(i) );
}
// Chiudiamo il file
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Quello che ci rimane da vedere ora è come recuperare le informazioni salvate nel file. Come ci si può facilmente aspettare, tutto quello che dovremo fare è creare un ObjectInputStream che vada a leggere il file creato precedentemente. Ciascun oggetto salvato nel file contniene un’istanza della classe Record, tranne il primo: nel primo abbiamo inserito un oggetto Integer che ci serve per sapere quante istanze di Record ci sono nel file. Questo è un semplice trucco per evitarci troppi problemi in fase di lettura:
public void leggiDipendenti() {
try {
// Apriamo il file
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("dipendenti.dat")
);
// Leggiamo il numero di dipendenti salvati
int numDip = ((Integer) ois.readObject()).intValue();
// Ora usiamo questo numero in un ciclo for di letture
for(int i=0; i<numDip; i++) {
dipendenti.add( (Record) ois.readObject() );
}
// Chiudiamo il file
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Come possiamo vedere da questi due esempi, un file ByteCode può contenere diversi tipi di oggetto (nel nostro caso, un Integer e tanti Record). L’importante è capire che possono essere salvati su file solamente “oggetti” e non tipi di dato primitivo (anche se questi possono essere presenti nell’oggetto che andiamo a serializzare), per ciascuno dei quali è prevista l’apposita classe wrapper. Quello da fare sempre, è di inserire come prima informazione, il numero di oggetti che saranno scritti successivamente. In questo modo sarà più facile recuperarli esattamente in futuro.
Non è, ovviamente, l’unico modo di procedere. Si può operare in modi differenti:
-
Testando la fine del file: per far questo è necessario utilizzare il metodo available() che ritorna il numero di byte che è ancora possibile leggere senza interruzioni. Ovviamente è necessario utilizzarlo come condizione di un ciclo while, condizione che dovrà essere testata ad ogni lettura (e non è molto performante effettuare test sui file);
-
Catturando una EOFException: questo implica di continuare a leggere finchè non viene sollevata questa eccezione. Ovviamente l’eccezione andrà gestita separatamente e l’intera struttura del blocco di lettura va rivista a questo scopo (visibilità delle variabili, ritorno da eccezione, ecc.).
-
Calcolando il numero di oggetti a runtime: la procedura non è bellissima a vedersi, ma può funzionare. Si controlla da subito la dimensione del file; si legge il primo oggetto e si controlla quanti byte sono stati letti (dimnesione iniziale – valore di available() ); si stima quanti altri oggetti ci sono dividendo il valore di available() per il numero di byte letti all’inizio.
Insomma, ognuno si può inventare quello che vuole. Io credo che la soluzione più pulita sia quella di memorizzare come primo oggetto un intero che indica chiaramente quanti altri oggetti incontrerò nella lettura.
La serializzazione, come detto all’inizio, è di fondamentale importanza anche per la trasmissione delle informazioni in rete. Pensiamo, ad esempio, ad una chat, dove la comunicazione tra client e server avviene tramite lo scambio di messaggi. Vi saranno messaggi che rappresentano i veri e propri messaggi che gli utenti della chat si scambiano, ma vi sono anche messaggi di utilità per client e server: alcuni esempi sono la comunicazione del nome utente e la password, l’informazione sulla lista di utenti attivi, le comunicazioni di arrvo di nuovi utenti o abbandono di altri, ecc. Ciascun messaggio può essere rappresentato da una classe “Messaggio” che incapsula tutte le caratteristiche richieste e che rappresenta il protocollo di comunicazione tra client e server: tale classe andrà ovviamente serializzata.
Concludendo, quindi, la serializzazione è un processo molto importante e utile in tutti i casi in cui si debba tenere traccia dello stato degli oggetti o quando essi devono essere inviati nella rete. Tutto quello che è necessario fare è implementare l’interfaccia Serializable, tenendo presente che all’interno di classi che implementano Serializable possono essere inseriti solo oggetti di tipo primitivo o serializzabili a loro volta. La maggior parte delle classi del core Java sono già serializzabili e pure tutti gli array sono automaticamente serializzabili, quindi gran parte del lavoro è già stato fatto a monte.



La Serializzazione Java…
Il meccanismo della serializzazione consente di salvare oggetti Java all’interno di file (File di tipo ByteCode) oppure di trasmetterli in rete come informazioni a se stanti….
La Serializzazione Java | FaceitWeb.com…
Il meccanismo della serializzazione consente di salvare oggetti Java all’interno di file (File di tipo ByteCode) oppure di trasmetterli in rete come informazioni a se stanti….
In realtà, la tecnica standard implementata da Java è molto buona e performante, tuttavia gli sviluppatori che portano avanti il progetto hanno deciso di rilasciare una struttura seriale anche per questo linguaggio, sia per offrire un modo alternativo a quello già disponibile per la gestione degli oggetti, sia perché si sta cercando di ottenere una piattaforma stabile dotata di serializzazione binaria portabile.
L’OpenNxSerialization fornisce un framework dotato di un meccanismo di serializzazione degli oggetti molto facile da usare e soprattutto veloce, in grado di sostituire la serializzazione di default.
Per quanto riguarda l’effettiva implementazione di questa tecnica, esistono principalmente due modi: il primo è quello di scrivere un surrogato che vada bene per il tipo di oggetto trattato e registrarlo all’interno del framework, il secondo si basa, invece, sull’utilizzo di INxSerializable.
Serializzazione in Java con OpenNxSerialization (Programmazione.it)