Ecco un’altra delle comodità offerte dal linguaggio Java: la serializzazione.
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 che faccio sempre io (e che consiglio vivamente) è di inserire sempre, 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:
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. Non mi rimane che augurarvi, quindi, una Buona Programmazione.
Tags:file java rete salvare Serializable serializzazione trasmissione
4 Risposte
cyberflaz
December 4th, 2007 at 16:34
1Compliments, bello (e utile) articolo.
LeleFT
December 4th, 2007 at 16:48
2@cyberflaz
Ti ringrazio. Avrei voluto approfondire l’argomento della serializzazione nell’ambito della trasmissione in rete, ma l’articolo sarebbe diventato troppo lungo e tedioso. Credo che approfondirò più avanti, riprendendo l’esempio dei messaggi della chat.
BradipoMissile
December 5th, 2007 at 02:23
3@ cyberflaz
Grazie a te per i complimenti (riferiti ovviamente all’operato di LeleFT!).
Una piccola domanda a carattere di sondaggio demoscopico: come hai conosciuto il nostro blog?!?
Andrea
September 20th, 2008 at 20:02
4Complimenti, completa e facile da comprendere!!
RSS Iscriviti ai Feed dei commenti di questo articolo · TrackBack URI
Lascia un commento... please!!!
Autori
Categories
Calendario
Tag Cloud
Sondaggio
Blog Amici
Gente "Avanti"
Siti Interessanti
Benvenuto!
Pensieri / Aggiornamenti
Qualche statistica
Blog segnalato su
Recent Posts
Recent Comments
Meta