![]() ![]() |
|
![]() ![]() |
Abbiamo visto che Java fornisce classi per accedere a dati memorizzati in un file in formato
Più in generale, Java fornisce nel package java.io [locale,
Medialab,
Sun]
classi e metodi per scrivere e leggere dati che possono
risiedere ovunque: in un file, su disco, da qualche parte
nella rete, in memoria, o in un altro programma.
Per leggere dati da una sorgente, un programma "apre
uno stream" (cioè una sequenza di dati)
associata alla sorgente, e legge in modo sequenziale i dati:
![]() Analogamente, un programma può inviare dati ad una destinazione esterna aprendo uno stream associato ad essa, e scrivendoci i dati in modo sequenziale:
![]()
|
![]() ![]() |
|
![]() ![]() |
Le seguenti classi definiscono
stream di varia natura, fornendo le primitive read() e write() per leggere o
scrivere caratteri o byte.
|
![]() ![]() |
|
![]() ![]() |
Java fornisce molte altre classi
che consentono un accesso più ad alto livello a
dati esterni. Ne abbiamo già viste alcune per
scrivere e leggere dati strutturati da file.
Queste classi vengono usate come wrapper di
stream basici, e forniscono le funzionalità
aggiuntive in modo indipendente dal tipo di stream basico
cui vengono associate. Tuttavia sono diverse per stream di caratteri
o di byte.
|
![]() ![]() |
|
![]() ![]() |
Le classi ObjectInputStream e ObjectOutputStream definiscono
streams (basati su streams di byte) su cui si possono leggere e
scrivere oggetti.
La scrittura e la lettura di oggetti va sotto il nome di object serialization, poiché si basa sulla possibilità di scrivere lo stato di un oggetto in una forma sequenziale, sufficiente per ricostruire l'oggetto quando viene riletto. La serializzazione di oggetti viene usata principalmente in due modi:
Per utilizzare correttamente questo meccanismo occorre sapere:
|
![]() ![]() |
|
![]() ![]() |
Il seguente esempio mostra come si scrivono alcuni oggetti su di un ObjectOutputStream che incapsula
il file (binario) theTime.
L'oggetto new Date()
rappresenta la data corrente (con precisione in millisecondi).
Se si scrive in un ObjectOutputStream con il metodo writeObject un oggetto che ha puntatori ad altri oggetti, allora tutti gli oggetti raggiungibili, sia direttamente che transitivamente, vengono scritti nello stream, in modo da mantenere le relazioni tra di essi. Oltre al metodo writeObject(), la classe ObjectOutputStream fornisce metodi per scrivere elementi dei tipi di dati primitivi, come writeInt, writeFloat, writeChar eccetera. Il metodo writeObject lancia un'eccezione NotSerializableException se cerca di serializzare un oggetto che non implementa l'interfaccia Serializable. Per leggere degli oggetti da uno stream, si usa in modo simmetrico la classe ObjectInputStream/ Il prossimo esempio legge dal file chiamato theTime gli oggetti di tipo String e Date che erano stati scritti dal programma WriteObject1:
Naturalmente gli oggetti devono essere riletti nell'ordine in cui erano stati scritti. Si noti che il tipo di readObject è Object e quindi serve un cast. Il metodo può lanciare l'eccezione controllata ClassNotFoundException. La classe ObjectInputStream ha naturalmente anche metodi per leggere elementi di tipi di dati elementari, come readInt, readFloat, e readChar. |
![]() ![]() |
|
![]() ![]() |
Un oggetto è serializzabile solo se la sua classe implementa
l'interfaccia Serializable.
Quindi se si vuole che le istanze di una classe che state scrivendo
siano serializzabili, è sufficiente dichiarare che la classe
implementa Serializable.
Poiché questa intefaccia non ha metodi, non occorre fare altro.
La serializzazione delle istanze di una classe viene gestita dal metodo defaultWriteObject della classe ObjectOutputStream. Questo metodo scrive automaticamente tutto ciò che è richiesto per ricostruire le istanze di una classe, e cioè
Per molte classi questo comportamento è soddisfacente. A volte, tuttavia, il programmatore può volere un maggior controllo su questo meccanismo. Si può personalizzare la serializzazione per proprie classi fornendo due metodi d'istanza: writeObject e readObject. Il metodo writeObject controlla come l'oggetto deve essere salvato, ed è usato tipicamente per inserire informazioni aggiuntive allo stream. Il metodo readObject di solito o legge le informazioni scritte dal corrispondente metodo writeObject, oppure può essere usato per aggiornare lo stato dell'oggetto dopo che è stato ricostruito. Il metodo writeObject deve essere scritto nel modo sequente: deve invocare come prima cosa il metodo defaultWriteObject dello stream per effettuare la serializzazione default:
Il metodo readObject deve leggere tutto ciò che è stato scritto dal metodo writeObject, nello stesso ordine in cui era stato scritto. Inoltre, il metodo readObject può effettuare ulteriori elaborazioni o aggiornare lo stato dell'oggetto in qualche modo. Questo è l'aspetto tipico di un metodo readObject che corrisponde al metodo writeObject visto sopra:
I metodi writeObject e readObject sono responsabili solo per la serializzazione della classe in cui sono definiti. Ogni eventuale serializzazione richiesta dalla superclasse è gestita automaticamente. Java fornisce comunque un meccanismo per coordinare la serializzazione di una classe con quella della superclasse, utilizzando l'interfaccia Externalizable (che non vedremo). La serializzazione di oggetti di una classe può causare problemi di sicurezza, poiché informazioni private o sensibili possono essere accedute da altri. Per evitare ciò ci sono due modi semplici:
|