We have covered how to persist the object state to a
disk file. In many situations, the classes in a program evolve over time. When
a class definition changes, the object’s data saved with previous versions of
the class becomes mostly unreadable. Versioning objects can manage this kind of
situation where we are trying to read from an older version of the class. We
will demonstrate this problem with a typical example in a practical situation.
Program Code
Product Class: Program to serialize the product class
Consider the program shown here:
import java.io.*;
public class ProductWriter {
public static void main(String args[]) throws IOException {
Product p1 = new Product(100);
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream( "product.dat"));
os.writeObject(p1);
os.close();
}
}
class Product implements Serializable {
private float price;
private float tax;
public Product(float price) {
this.price = price;
tax = (float) (price * 0.20);
}
}
· The program declares a Product class that has two fields, price
and tax,
of type float.
· The main function constructs an instance of Product and saves it to a
physical disk file.
· After the data is saved at a later time, we want to read this data from the disk file and use it in future applications. For this, we write a ProductReader class, as shown here
Product Reader Class: Program to Read Serialized Product Data
import java.io.*;
public class ProductReader {
public static void main(String args[]) throws Exception {
ObjectInputStream is = new ObjectInputStream(new FileInputStream( "product.dat"));
Product p1 = (Product) is.readObject();
System.out.println(p1.toString());
}
}
· The program simply opens the
previously created data file, reads its data into the Product object, and
prints its contents. When we run the program, output similar to the following
is shown:
Product@9304b1
· Whatever is printed to the
console is definitely not what we want. We want the product’s price
and
tax to be printed to the console.
· Therefore, we will now
override the default toString method of the Product
class. The modified Product class
definition is shown here.
Modified Product Class
class Product implements Serializable {
private float price;
private float tax;
public Product(float price) {
this.price = price;
tax = (float) (price * 0.20);
}
public String toString() {
return ("Price:" + price + " Tax:" + tax);
}
}
Exception in thread "main" java.io.InvalidClassException: Product; local class
incompatible: stream classdesc serialVersionUID = -4609301823165882715, local
class serialVersionUID = -3424249794808075076
This
is because the Product state was
saved with the previous version of the Product class. Java assigns a unique identifier (serialversionUID) to every serializable class during
compilation.
Thus,
when we change the class definition, the object state information we had saved
becomes incompatible with the new version of the class. This problem can be
solved by adding the serialversionUID
(which represents the stream unique identifier, or SUID) of the original class
to the modified class definition. To determine the serialversionUID of a class, run the following command on the
command prompt:
C:\360\io> serialver Product
Product: static final long serialVersionUID = -3424249794808075076L;
static
final long serialVersionUID = -3424249794808075076L;
Note
that the ID generated on our machine will differ from what’s shown here. The
modified Product class is shown in here:
class Product implements Serializable {
private float price;
private float tax;
static final long serialVersionUID = -3424249794808075076L;
public Product(float price) {
this.price = price;
tax = (float) (price * 0.20);
}
public String toString() {
return ("Price:" + price + " Tax:" + tax);
}
}
Recompile
the Product class and re-run our ProductReader application. We will see the following
output:
Price:100.0
Tax:20.0
The
new class now uses the serialVersionUID
of the earlier class. The compiler in this case does not generate the new ID
for the modified class. Thus, the objects created with earlier versions now
remain compatible with the newer versions as far as serialization is concerned.
Note:
If we:
·
modify the class fields
·
add a new field
·
delete an existing field
the object’s saved state will become incompatible with the earlier version of the class. The earlier objects will still be readable with the modified class definitions as long as you maintain the same serialVersionUID across the different versions of a class. Our program should take care of the newly added fields or the missing fields when we read the data stored in earlier versions.
Allen Scott
01-Apr-2017