In order to be serializable, a class should implement the interface "Serializable".
eg.
class Person implements Serializable {
// serializable class' version number
private static final long serialVersionUID = 1L;
String name;
int age;
}
The class "ObjectOutputStream" has to be used in order to serialize an object.
eg.
// The object to serialize
Person person = new Person();
person.name = "Joe";
person.age = 19;
// We serialize the "person" object
try{
FileOutputStream fos = new FileOutputStream("/tmp/person.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
// Serialization of the "person" object
os.writeObject(person);
// We close the stream
os.close();
}catch(java.io.IOException ex){
System.out.println("Serialization failed: "+ex.getMessage());
}
Deserialization
Deserialization is the action to restore the state of a serialized object.
The class "ObjectInputStream" has to be used in order to deserialize an object.
eg.
// We deserialize the file "person.ser"
try{
FileInputStream fis = new FileInputStream("/tmp/person.ser");
ObjectInputStream os = new ObjectInputStream(fis);
try{
Person person = (Person)os.readObject();
// We display the object's info
System.out.println("Person's name: "+person.name);
System.out.println("Person's age: "+person.age);
}catch(ClassNotFoundException ex){
System.out.println("Deserialization failed while reading object: "+ex.getMessage());
}
// We close the stream
os.close();
}catch(java.io.IOException ex){
System.out.println("Deserialization failed: "+ex.getMessage());
}
Output:
Person's name: Joe
Person's age: 19
Serialization and deserialization of many objects and primitives
Note that the classes "ObjectOutputStream" and "ObjectInputStream" allow you to serialize and deserialize one or many objects and primitives in the same file. The order in which the objects and primitives are serialized matter!
Example of serialization of many objects and primitives:
// A person object
Person person = new Person();
person.name = "Joe";
person.age = 19;
// We serialize the "person" object
try{
FileOutputStream fos = new FileOutputStream("/tmp/objectsAndPrimitives.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
// The order in which we will write objects and primitives is important
// Serialization of the "person" object
os.writeObject(person);
// Serialization of a string
os.writeObject("This is a string");
// We add few primitives
os.writeInt(199);
os.writeBoolean(true);
// We close the stream
os.close();
}catch(java.io.IOException ex){
System.out.println("Serialization failed: "+ex.getMessage());
}
Example of deserialization of many objects and primitives:
Note that the order in which the objects and primitives have been serialized matter.
// We deserialize the file "objectsAndPrimitives.ser"
try{
FileInputStream fis = new FileInputStream("/tmp/objectsAndPrimitives.ser");
ObjectInputStream os = new ObjectInputStream(fis);
try{
// We have to call the methods "read*" (like "readObject()", "readInt()", ..) in the same order
// than when we used the methods "write*" (like "writeObject()", "writeInt()", ..) (see the previous example)
// We deserialize the "person" object: it has been serialized as first object
Person person = (Person)os.readObject();
// We deserialize the String object: it has been serialized as second object
String aString = (String)os.readObject();
// The primitives
int anInteger = os.readInt();
boolean aBoolean = os.readBoolean();
// We display person's info
System.out.println("Person's name: "+person.name);
System.out.println("Person's age: "+person.age);
// The serialized string
System.out.println("string: "+aString);
// The primitives
System.out.println("int: "+anInteger);
System.out.println("boolean: "+aBoolean);
}catch(ClassNotFoundException ex){
System.out.println("Deserialization failed while reading object: "+ex.getMessage());
}
// We close the stream
os.close();
}catch(java.io.IOException ex){
System.out.println("Deserialization failed: "+ex.getMessage());
}
Serialization of a graph of objects
When you serialize an object, this object may use the references of other objects in its implementation. In this case JVM (Java Virtual Machine) will try to serialize the entire "graph of objects" that the main object to serialize depends of. If one of these objects, part of the objects graph, does not implement the interface "Serializable" then the compilation succeed but a runtime exception will be thrown!
Eg.
// The class "Car" is not Serializable
class Car {
String model;
int yearProduction;
Car(String pModel, int pYearProduction){
this.model = pModel;
this.yearProduction = pYearProduction;
}
}
class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
int age;
Car personalCar;
}
// The object to serialize
Person person = new Person();
person.name = "Joe";
person.age = 19;
// A "Person" HAS-A "Car"
person.personalCar = new Car("Renault 19", 1996);
// We serialize the "person" object
try{
FileOutputStream fos = new FileOutputStream(FILE_NAME);
ObjectOutputStream os = new ObjectOutputStream(fos);
// Serialization of the "person" object
os.writeObject(person);
// We close the stream
os.close();
}catch(java.io.IOException ex){
System.out.println("Serialization failed: ");
ex.printStackTrace();
}
Compile fine but a runtime Exception is thrown:java.io.NotSerializableException: Car
Transient keyword
In the previous example, the class "Car" is not serializable. If you can not modify the code source of the class "Car" in order to make it serializable, you can still declare the instance variable 'personalCar' as transient. This will prevent JVM to try serializing the instance of "Car" when you serialize an object of the class "Person".
Eg.
class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
int age;
transient Car personalCar;
}
If you serialize an object of the class "Person", JVM will not serialize the value of the instance 'personalCar', instead it will set it as "null".
The privates methods "readObject" and "writeObject"
If you can manually save the state of an object which is not serializable with using the following methods:
private void writeObject(ObjectOutputStream os) : it is automatically called by JVM during an object serialization
private void readObject(ObjectInputStream os) : it is automatically called by JVM during the deserialization to an object
Let's take the example of the "Person" class. This class has a reference to a Car. As "Car" class is not serializable, we marked the instance variable 'personnalCar' as transient.
Eg.
class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
int age;
transient Car personalCar;
}
What we want is to manually save the state of the object "Car" during the serialization of a "Person" object. To achieve this we have to add the private method 'writeObject' in the class Person:
class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
int age;
transient Car personalCar;
private void writeObject(ObjectOutputStream os) {
try {
os.defaultWriteObject(); // Serialize the state of the current Person object (except transient instance variables and static variables)
String carModel = null;
int carYearProduction = 0;
// We set the state of the "Car" object, if this object exists
if(this.personalCar != null){
carModel = this.personalCar.model;
carYearProduction = this.personalCar.yearProduction;
}
// We manually save the state of a "Car" object
os.writeObject(carModel);
os.writeInt(carYearProduction);
} catch(Exception ex) {
System.out.println("Serialization in the private method 'writeObject' has failed: ");
ex.printStackTrace();
}
}
During the deserialization of a Person object, we want to manually create an instance of a Car object and set it to the instance variable 'personalCar'. To achieve this we have to add the private method 'readObject' in the class Person:
class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
int age;
transient Car personalCar;
private void readObject(ObjectInputStream os) {
try{
os.defaultReadObject(); // Deserialize the state of the current "Person" object
// We get the state of a "Car" object
String carModel = (String)os.readObject();
int carYearProduction = os.readInt();
// We create a new "Car" object
if(carModel != null){
this.personalCar = new Car(carModel, carYearProduction);
}
}catch(Exception ex){
System.out.println("Deserialization in the private method 'readObject' has failed: ");
ex.printStackTrace();
}
}
private void writeObject(ObjectOutputStream os) {
try{
// Serialize the state of the current "Person" object
// (except transient instance variables and static variables)
os.defaultWriteObject();
String carModel = null;
int carYearProduction = 0;
// We set the state of the "Car" object, if this object exists
if(this.personalCar != null){
carModel = this.personalCar.model;
carYearProduction = this.personalCar.yearProduction;
}
// We manually save the state of "Car" object
os.writeObject(carModel);
os.writeInt(carYearProduction);
}catch(Exception ex){
System.out.println("Serialization in the private method 'writeObject' has failed: ");
ex.printStackTrace();
}
}
}
With the new implementation of the "Person" class, each time a "Person" object is serialized and deserialized, the state of the "Car" object will be maintained.
Serialization an inheritance
If a superclass implements "Serializable" then all of its subclass do too.
If a superclass does not implements "Serializable" then when a subclass's object is deserialized the unserializable superclass's constructor runs!
Therefore, if a class "Child" has a superclass "Parent" which is not serializable and if you serialize an object of "Child" then the state of "Parent" will not be serialized! During the deserialization of an object of "Child" the constructor of the superclass "Parent" will run! Therefore make sure that a superclass is serializable. In fact make sure that the entire inheritance tree is serializable.
Version number of a "serializable" class
From Java's API:
The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender's class, then deserialization will result in an InvalidClassException. A serializable class can declare its own serialVersionUID explicitly by declaring a field named "serialVersionUID" that must be static, final, and of type long:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class--serialVersionUID fields are not useful as inherited members. Array classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.
Source: http://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html