SCJP : Serializable Interface
Develop code that serializes and/or de-serializes objects using the following APIs from java.io:
DataInputStream, DataOutputStream, FileInputStream,
FileOutputStream, ObjectInputStream,
ObjectOutputStream and Serializable.
DataInputStream
A data input stream lets an application read primitive Java data types from an underlying input stream in a
machine-independent way. An application uses a data output stream to write data that can later be read by a
data input stream.
A DataInputStream takes as a constructor
an InputStream.
Some methods:
public final boolean readBoolean() throws IOException
public final byte readByte() throws IOException
public final int readUnsignedByte() throws IOException
public final short readShort() throws IOException
public final int readUnsignedShort() throws IOException
public final char readChar() throws IOException
public final int readInt() throws IOException
public final long readLong() throws IOException
public final float readFloat() throws IOException
public final double readDouble() throws IOException
FileInputStream fis = new FileInputStream("test.txt");
DataInputStream dis = new DataInputStream(fis);
boolean value = dis.readBoolean();
DataOutputStream
A data output stream lets an application write primitive Java data types to an output stream in
a portable way. An application can then use a data input stream to read the data back in.
A DataOutputStream takes as a constructor an
OutputStream.
Some methods:
public final void writeBoolean(boolean v) throws IOException
public final void writeByte(int v) throws IOException
public final void writeShort(int v) throws IOException
public final void writeChar(int v) throws IOException
public final void writeInt(int v) throws IOException
public final void writeLong(long v) throws IOException
public final void writeFloat(float v) throws IOException
public final void writeDouble(double v) throws IOException
public final void writeBytes(String s) throws IOException
public final void writeChars(String s) throws IOException
FileInputStream
A FileInputStream obtains input bytes from a file in a file system. What files
are available depends on the host environment.
FileInputStream is meant for reading streams of raw bytes such as image
data. For reading streams of characters, consider using FileReader.
Some methods:
// Reads a byte of data from this input stream
public int read() throws IOException
public int read(byte[] b) throws IOException
public int read(byte[] b, int off, int len) throws IOException
FileInputStream fis = new FileInputStream("test.txt");
FileOutputStream
A file output stream is an output stream for writing data to a File or
to a FileDescriptor. Whether or not a file is available or may be
created depends upon the underlying platform. Some platforms, in particular, allow a file to be
opened for writing by only one FileOutputStream (or other file-writing object)
at a time. In such situations the constructors in this class will fail if the file involved is already open.
FileOutputStream is meant for writing streams of raw bytes such as image data.
For writing streams of characters, consider using FileWriter.
Some methods:
// Writes the specified byte to this file output stream
public void write(int b) throws IOException
public void write(byte[] b) throws IOException
public void write(byte[] b, int off, int len) throws IOException
FileOutputStream fos = new FileOutputStream("testout.txt");
// append
FileOutputStream fos = new FileOutputStream("testout.txt", true);
ObjectInputStream
An ObjectInputStream deserializes primitive data and objects previously written using
an ObjectOutputStream. ObjectOutputStream and
ObjectInputStream can provide an application with persistent storage for graphs
of objects when used with a FileOutputStream and FileInputStream
respectively. ObjectInputStream is used to recover those objects
previously serialized. Other uses include passing objects between hosts using a socket stream or for
marshaling and unmarshaling arguments and parameters in a remote communication system.
ObjectInputStream ensures that the types of all objects in the graph created
from the stream match the classes present in the Java Virtual Machine. Classes are loaded as required using
the standard mechanisms.
Only objects that support the java.io.Serializable or
java.io.Externalizable interface can be read from streams.
The method readObject is used to read an object from the stream. Java's safe casting
should be used to get the desired type. In Java, strings and arrays are objects and are treated as
objects during serialization. When read they need to be cast to the expected type.
Primitive data types can be read from the stream using the appropriate method on DataInput.
The default deserialization mechanism for objects restores the contents of each field to the value and type it
had when it was written. Fields declared as transient or
static are IGNORED by the deserialization process. References
to other objects cause those objects to be read from the stream as necessary. Graphs of objects are restored correctly
using a reference sharing mechanism. New objects are always allocated when deserializing, which prevents existing
objects from being overwritten.
Reading an object is analogous to running the constructors of a new object. Memory is allocated for the object
and initialized to zero (NULL). No-arg constructors are invoked for the non-serializable classes and then
the fields of the serializable classes are restored from the stream starting with the serializable class closest
to java.lang.Object and finishing with the object's most specific class:
public class A {
public int aaa = 111;
public A() {
System.out.println("A");
}
}
Class B extends non-serializable class A:
import java.io.Serializable;
public class B extends A implements Serializable {
public int bbb = 222;
public B() {
System.out.println("B");
}
}
The client code:
public class Client {
public static void main(String[] args) throws Exception {
B b = new B();
b.aaa = 888;
b.bbb = 999;
System.out.println("Before serialization:");
System.out.println("aaa = " + b.aaa);
System.out.println("bbb = " + b.bbb);
ObjectOutputStream save = new ObjectOutputStream(new FileOutputStream("datafile"));
save.writeObject(b); // Save object
save.flush(); // Empty output buffer
ObjectInputStream restore = new ObjectInputStream(new FileInputStream("datafile"));
B z = (B) restore.readObject();
System.out.println("After deserialization:");
System.out.println("aaa = " + z.aaa);
System.out.println("bbb = " + z.bbb);
}
}
The client's output:
A
B
Before serialization:
aaa = 888
bbb = 999
A
After deserialization:
aaa = 111
bbb = 999
For example to read from a stream as written by the example in ObjectOutputStream:
FileInputStream fis = new FileInputStream("test.tmp");
ObjectInputStream ois = new ObjectInputStream(fis);
int i = ois.readInt();
String today = (String) ois.readObject();
Date date = (Date) ois.readObject();
ois.close();
Classes control how they are serialized by implementing either the
java.io.Serializable or
java.io.Externalizable interfaces.
Implementing the Serializable interface allows object serialization to save and restore
the entire state of the object and it allows classes to evolve between the time the stream is written and the
time it is read. It automatically traverses references between objects, saving and restoring entire graphs.
Serializable classes that require special handling during the serialization and deserialization process should
implement the following methods:
private void writeObject(java.io.ObjectOutputStream stream) throws IOException;
private void readObject(java.io.ObjectInputStream stream) throws IOException,
ClassNotFoundException;
The readObject method is responsible for reading and restoring the state of the object for
its particular class using data written to the stream by the corresponding
writeObject method. The method does not need to concern itself with the state
belonging to its superclasses or subclasses. State is restored by reading data from the
ObjectInputStream for the individual fields and making assignments to the
appropriate fields of the object. Reading primitive data types is supported by DataInput.
Serialization does not read or assign values to the fields of any object that does not implement the
java.io.Serializable interface. Subclasses of Objects that are not serializable can
be serializable. In this case the non-serializable class must have a no-arg constructor to allow its
fields to be initialized. In this case it is the responsibility of the subclass to save and restore the state
of the non-serializable class. It is frequently the case that the fields of that class are
accessible (public, package, or protected) or that there are
get and set methods that can be used to restore the state.
Implementing the Externalizable interface allows the object to assume complete control
over the contents and format of the object's serialized form. The methods of the
Externalizable interface, writeExternal and readExternal,
are called to save and restore the objects state. When implemented by a class they can write and read their own
state using all of the methods of ObjectOutput and ObjectInput. It is
the responsibility of the objects to handle any versioning that occurs.
Enum constants are deserialized differently than ordinary serializable or externalizable objects. The serialized form
of an enum constant consists solely of its name; field values of the constant
are not transmitted. To deserialize
an enum constant, ObjectInputStream reads the constant name
from the stream; the deserialized
constant is then obtained by calling the static method Enum.valueOf(Class, String) with
the enum constant's base type and the received constant name as
arguments. Like other serializable or externalizable
objects, enum constants can function as the targets of back references
appearing subsequently in the serialization
stream. The process by which enum constants are deserialized CANNOT be
customized: any class-specific
readObject, readObjectNoData, and readResolve
methods defined by enum types are ignored during deserialization.
ObjectOutputStream
An ObjectOutputStream writes primitive data types and graphs of Java objects to
an OutputStream. The objects can be read (reconstituted) using an
ObjectInputStream. Persistent storage of objects can be accomplished by using a
file for the stream. If the stream is a network socket stream, the objects can be reconstituted on another
host or in another process.
Only objects that support the java.io.Serializable interface can be written to streams.
The class of each serializable object is encoded including the class name and signature of the class, the
values of the object's fields and arrays, and the closure of any other objects referenced from the initial objects.
The method writeObject is used to write an object to the stream. Any object,
including Strings and arrays, is written with writeObject. Multiple objects or primitives
can be written to the stream. The objects MUST be read back from the corresponding
ObjectInputStream with the SAME types and in the SAME order as they were written.
Primitive data types can also be written to the stream using the appropriate methods from
DataOutput. Strings can also be written using the
writeUTF method.
The default serialization mechanism for an object writes the class of the object, the class signature, and
the values of all non-transient and non-static fields. References to other objects (except in transient or
static fields) cause those objects to be written also. Multiple references to a
single object are encoded using a reference sharing mechanism so that graphs of objects can be
restored to the same shape as when the original was written.
For example to write an object that can be read by the example in ObjectInputStream:
FileOutputStream fos = new FileOutputStream("test.tmp");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeInt(12345);
oos.writeObject("Today");
oos.writeObject(new Date());
oos.close();
Classes that require special handling during the serialization and deserialization process must implement
special methods with these exact signatures:
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException;
private void writeObject(java.io.ObjectOutputStream stream)
throws IOException
The writeObject method is responsible for writing the state of the object for its
particular class so that the corresponding readObject method can restore it. The
method does not need to concern itself with the state belonging to the object's superclasses or subclasses.
State is saved by writing the individual fields to the ObjectOutputStream using the
writeObject method or by using the methods for primitive data types
supported by DataOutput.
Serialization does not write out the fields of any object that does not implement the
java.io.Serializable interface. Subclasses of Objects that are not
serializable can be serializable. In this case the non-serializable class must have a
no-arg constructor to allow its fields to be initialized. In this case it is the responsibility of the
subclass to save and restore the state of the non-serializable class. It is frequently the case that the
fields of that class are accessible (public, package, or protected) or
that there are get and set methods that can be used to restore the state.
Serializable
Serializability of a class is enabled by the class implementing the
java.io.Serializable interface. Classes that do not implement this interface
will not have any of their state serialized or deserialized. All subtypes of a serializable class are
themselves serializable. The serialization interface has NO methods or fields and serves only to identify
the semantics of being serializable.
To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility
for saving and restoring the state of the supertype's public, protected,
and (if accessible) package fields. The subtype may assume this responsibility only if the class it
extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a
class Serializable if this is not the case. The error will be detected at runtime.
During deserialization, the fields of non-serializable classes will be initialized using the public or
protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass
that is serializable. The fields of serializable subclasses will be restored from the stream.
When traversing a graph, an object may be encountered that does not support the
Serializable interface. In this case the
NotSerializableException will be thrown and will identify the class
of the non-serializable object.
Classes that require special handling during the serialization and deserialization process must implement
special methods with these exact signatures:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException;
Serialization
Imagine a graph of objects that lead from the object to be saved. The entire graph must be saved and restored:
Obj 1 --> Obj 2 --> Obj 3
\--> Obj 4 --> Obj 5
\ --> Obj 6
We need a byte-coded representation of objects that can be stored in a file external to Java programs,
so that the file can be read later and the objects can be reconstructed. Serialization provides a
mechanism for saving and restoring objects.
Serializing an object means to code it as an ordered series of bytes in such a way that it can be
rebuilt (really a copy) from that byte stream. Deserialization generates a new live object graph out
of the byte stream.
The serialization mechanism needs to store enough information so that the original object can be
recreated including all objects to which it refers (the object graph).
Java has classes (in the java.io package) that allow the creation of streams for
object serialization and methods that write to and read from these streams.
Only an object of a class that implements the EMPTY interface java.io.Serializable
or a subclass of such a class can be serialized.
What is saved:
The class of the object.
The class signature of the object.
All instance variables NOT declared transient.
Objects referred to by non-transient instance variables.
If a duplicate object occurs when traversing the graph of references, only ONE copy is saved, but references are coded
so that the duplicate links can be restored.
Saving an object (an array of Fruit)
Open a file and create an ObjectOutputStream object.
ObjectOutputStream save = new ObjectOutputStream(new FileOutputStream("datafile"));
Make Fruit serializable:
class Fruit implements Serializable
Write an object to the stream using writeObject().
Fruit [] fa = new Fruit[3];
// Create a set of 3 Fruits and place them in the array.
...
save.writeObject(fa); // Save object (the array)
save.flush(); // Empty output buffer
Restoring the object
Open a file and create an ObjectInputStream object.
ObjectInputStream restore = new ObjectInputStream(new FileInputStream("datafile"));
Read the object from the stream using readObject() and then cast it to its
appropriate type.
Fruit[] newFa;
// Restore the object:
newFa = (Fruit[])restore.readObject();
or
Object ob = restore.readObject();
When an object is retrieved from a stream, it is validated to ensure that it can be rebuilt
as the intended object. Validation may fail if the class definition of the object has changed.
A class whose objects are to be saved must implement interface Serializable,
with no methods, or the Externalizable interface, with two methods. Otherwise,
runtime exception will be thrown:
Exception in thread "main" java.io.NotSerializableException: Bag
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at Client.main(Client.java:17)
The first superclass of the class (maybe Object) that is not serializable
must have a no-parameter constructor.
The class must be visible at the point of serialization.
The implements Serializable clause acts as a tag indicating the possibility of serializing the
objects of the class.
All primitive types are serializable.
Transient fields (with transient modifier) are NOT serialized, (i.e., not saved or restored).
A class that implements Serializable must mark transient fields
of classes that do not support serialization (e.g., a file stream).
Because the deserialization process will create new instances of the objects. Comparisons
based on the "==" operator MAY NO longer be valid.
Main saving objects methods:
public ObjectOutputStream(OutputStream out) throws IOException
public final void writeObject(Object obj) throws IOException
public void flush() throws IOException
public void close() throws IOException
Main restoring objects methods:
public ObjectInputStream(InputStream in) throws IOException, SecurityException
public final Object readObject() throws IOException, ClassNotFoundException
public void close() throws IOException
ObjectOutputStream and ObjectInputStream also implement the methods
for writing and reading primitive data and Strings from the interfaces
DataOutput and DataInput, for example:
writeBoolean(boolean b) <==> boolean readBoolean()
writeChar(char c) <==> char readChar()
writeInt(int i) <==> int readInt()
writeDouble(double d) <==> double readDouble()
No methods or class variables are saved when an object is serialized.
A class knows which methods and static data are defined in it.
Serialization example (client class):
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Client {
public static void main(String ... aaa) throws IOException, ClassNotFoundException {
Bag b = new Bag();
Bag a = null;
ObjectOutputStream save = new ObjectOutputStream(new FileOutputStream("datafile"));
System.out.println("Before serialization:");
System.out.println(b);
save.writeObject(b); // Save object
save.flush(); // Empty output buffer
ObjectInputStream restore = new ObjectInputStream(new FileInputStream("datafile"));
a = (Bag) restore.readObject();
System.out.println("After deserialization:");
System.out.println(a);
}
}
If the Bag class does not implement
Serializable:
import java.util.Arrays;
public class Bag {
Fruit[] fruits = new Fruit[3];
public Bag() {
fruits[0] = new Fruit("Orange");
fruits[1] = new Fruit("Apple");
fruits[2] = new Fruit("Pear");
}
public String toString() {
return "Bag of fruits :" + Arrays.toString(fruits);
}
}
We get the following runtime exception:
Before serialization:
Bag of fruits :[Fruit : Orange, Fruit : Apple, Fruit : Pear]
Exception in thread "main" java.io.NotSerializableException: Bag
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at Client.main(Client.java:17)
Now, make Bag class implement Serializable,
but Fruit class still is not Serializable:
public class Fruit {
String name = "";
public Fruit(String name) {
this.name = name;
}
public String toString() {
return "Fruit : " + name;
}
}
We get same exception but in class Fruit:
Before serialization:
Bag of fruits :[Fruit : Orange, Fruit : Apple, Fruit : Pear]
Exception in thread "main" java.io.NotSerializableException: Fruit
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeArray(Unknown Source)
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
...
We can ask not to serialize Fruit classes by making array of fruits
transient:
import java.io.Serializable;
import java.util.Arrays;
public class Bag implements Serializable {
transient Fruit[] fruits = new Fruit[3]; // do not save to disk
public Bag() {
fruits[0] = new Fruit("Orange");
fruits[1] = new Fruit("Apple");
fruits[2] = new Fruit("Pear");
}
public String toString() {
return "Bag of fruits :" + Arrays.toString(fruits);
}
}
Now the program is running without exceptions, but fruits are not restoring in the bag
after deserialization:
Before serialization:
Bag of fruits :[Fruit : Orange, Fruit : Apple, Fruit : Pear]
After deserialization:
Bag of fruits :null
Only when both Bag and Fruits are serializable,
we get expected output:
import java.io.Serializable;
import java.util.Arrays;
public class Bag implements Serializable {
Fruit[] fruits = new Fruit[3];
public Bag() {
fruits[0] = new Fruit("Orange");
fruits[1] = new Fruit("Apple");
fruits[2] = new Fruit("Pear");
}
public String toString() {
return "Bag of fruits :" + Arrays.toString(fruits);
}
}
import java.io.Serializable;
public class Fruit implements Serializable {
String name = "";
public Fruit(String name) {
this.name = name;
}
public String toString() {
return "Fruit : " + name;
}
}
The client's output now will be:
Before serialization:
Bag of fruits :[Fruit : Orange, Fruit : Apple, Fruit : Pear]
After deserialization:
Bag of fruits :[Fruit : Orange, Fruit : Apple, Fruit : Pear]
NOTE, static and transient fields are NOT serialized:
import java.io.Serializable;
public class MyClass implements Serializable {
transient int one;
private int two;
static int three;
public MyClass() {
one = 1;
two = 2;
three = 3;
}
public String toString() {
return "one : " + one + ", two: " + two + ", three: " + three;
}
}
This code saves class instance:
...
MyClass b = new MyClass();
ObjectOutputStream save = new ObjectOutputStream(new FileOutputStream("datafile"));
save.writeObject(b); // Save object
save.flush(); // Empty output buffer
...
Deserialize:
...
MyClass a = null;
ObjectInputStream restore = new ObjectInputStream(new FileInputStream("datafile"));
a = (MyClass) restore.readObject();
System.out.println("After deserialization:");
System.out.println(a);
...
The output:
After deserialization:
one : 0, two: 2, three: 0
As you can see, static and transient fields got the
default values for instance variables.
|