Dopo aver visto come leggere e scrivere byte singoli e a blocchi in un file binario (link), in quest’articolo vediamo un altro modo di operare con i file binari che dà la possibilità di leggere e scrivere direttamente variabili primitive e, addirittura, oggetti. Questa modalità di accesso ad un file binario, sfrutta la cosiddetta serializzazione che è una particolare funzionalità di Java che permette di tradurre le variabili e gli oggetti in uno stream di byte che può essere salvato in maniera persistente in un file binario. Questo stream di byte successivamente può essere riletto e trasformato attraverso un operazione inversa di de-serializzazione, per ottenere le variabili e gli oggetti originari.
Lettura e scrittura binaria di variabili primitive e oggetti
Ciò può essere realizzato utilizzando gli oggetti di classe ObjectOutputStream e ObjectInputStream del package java.io, istanziati a partire da oggetti delle classi FileOutputStream e FileInputStream già viste in un altro articolo sui file binari a cui si rimanda: link. Sintassi:
1 2 3 4 5 6 7 |
//creazione di uno stream di output serializzato FileOutputStream destinazione = new FileOutputStream("out.dat"); ObjectOutputStream destinazioneSerializzata = new ObjectOutputStream (destinazione); //creazione di uno stream di input de-serializzato FileInputStream sorgente = new FileInputStream("in.dat"); ObjectInputStream sorgenteSerializzata = new ObjectInputStream (sorgente); |
Le classi ObjectOutputStream e ObjectInputStream dispongono di metodi nella forma writeTipoDato e readTipoDato, rispettivamente, dove TipoDato può essere sostituito con Byte, Char, Int, Double, Object, ecc.. a seconda del tipo di dato che si deve scrivere/leggere, compreso un oggetto di un qualunque tipo di classe. In quest’ultimo caso però bisogna ricordarsi che la classe dell’oggetto deve necessariamente implementare l’interfaccia Serializable.
Vediamo un semplice esempio che utilizza la serializzazione e la de-serializzazione per scrivere e leggere un oggetto in un file binario.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.text.ParseException; public class Test { public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException, ParseException { //crea un alunno Alunno a1 = new Alunno("Pinco", "Pallino"); //aggiunge due voti all'alunno Voto v; v = new Voto("Orale", 5.5f, "10/010/16"); a1.aggiungiVoto(v); v = new Voto("Scritto", 7, "5/11/16"); a1.aggiungiVoto(v); //SCRIVE l'oggetto alunno in un FILE BIANARIO tramite la serializzazione ObjectOutputStream fbinarioOut = new ObjectOutputStream(new FileOutputStream("alunno.dat")); fbinarioOut.writeObject(a1); fbinarioOut.flush(); fbinarioOut.close(); //LEGGE l'oggetto alunno salvato nel file, tramite la deserializzazione ObjectInputStream fin = new ObjectInputStream(new FileInputStream("alunno.dat")); a1 = (Alunno) fin.readObject(); //Visualizza l'oggetto sul monitor, sfruttando (implicitamente) il suo metodo toString() System.out.println(a1); //Scrive l'oggetto in file di testo, sfruttando (implicitamente) il suo metodo toString() PrintWriter ftestoOut = new PrintWriter(new FileWriter("alunno.txt")); ftestoOut.println(a1); ftestoOut.close(); } } |
La classe di test precedente utilizza la classe Voto seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import java.io.Serializable; import java.text.DateFormat; import java.text.ParseException; import java.util.Date; import java.util.Locale; public class Voto implements Serializable { private String descrizione; private float voto; private Date data; public Voto(String descrizione, float voto, String dataShort) throws ParseException { this.descrizione = descrizione; this.voto = voto; DateFormat fdata = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ITALY); data = fdata.parse(dataShort); } public String getDescrizione() { return descrizione; } public float getVoto() { return voto; } @Override public String toString() { DateFormat fdata = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ITALY); String d = fdata.format(data); return d + " - " + descrizione + ": " + voto; } } |
e la classe Aunno seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import java.io.Serializable; public class Alunno implements Serializable { private String cognome; private String nome; private Voto [] voti; private int nv = 0; public Alunno(String cognome, String nome) { this.cognome = cognome; this.nome = nome; voti = new Voto[100]; } public void aggiungiVoto(Voto voto) { voti[nv] = voto; nv++; } @Override public String toString() { String s = cognome + " " + nome + ":" + "\r\n"; for(int i=0; i<nv; i++) s += voti[i].toString() + "\r\n"; return s; } } |
L’esecuzione del programma produce il file binario alunno.dat, che ovviamente non sarà leggibile con un semplice editor di testi. Esso, inoltre, viene letto tramite il processo inverso di de-serializzazione per produrre il file di testo alunno.txt con quanto letto e visualizzare il seguente output sulla console, in modo da testare il corretto funzionamento del codice scritto:
Il codice per le parti di serializzazione e de-serializzazione è commentato, per il resto è autoesplicativo. Si fa notare solo che entrambe le classi, Voto e Alunno, implementano l’interfaccia Serializable (righe evidenziate in giallo) affinché possano essere serializzate.
P.S. Per quanto riguarda le eccezioni che vengono propagate dalla classe di test con la clausola throws, si fa osservare che:
- ParseException, è legata alla conversione dell’oggetto di classe Date in stringa, che avviene attraverso il metodo parse() dell’oggetto di classe DateFormat nella classe Voto. (Per approfondimenti sulla classe Date si rimanda al seguente articolo: link).
- FileNotFoundException, deriva dall’oggetto FileInputStream ed è legata agli errori potenziali durante l’apertura del file.
- IOException, è legata a tutti gli errori potenziali durante le operazioni di lettura e scrittura dei file.
- ClassNotFoundException, deriva da altri problemi potenziali legati alla serializzazione che per brevità non vengono qui menzionati.
Un piccola precisazione, queste eccezioni piuttosto che essere propagate, così come è stato fatto nell’esempio di sopra nel main, andrebbero gestite. Per brevità e semplicità di trattazione non è stato fatto. Troppe cose da ricordare? Non preoccupatevi, gli attuali IDE come Netbeans o Eclipse ci aiutano con utilissimi suggerimenti.