Serialization - sivakrsna/Java-Interview GitHub Wiki
Serialization
Object serialization is the process of saving an object's state to a sequence of bytes, as well as the process of rebuilding those bytes into a live object at some future time. The Java Serialization API provides a standard mechanism for developers to handle object serialization. The API is small and easy to use, provided the classes and methods are understood. Discover the secrets of the Java Serialization API
Why
We all know the Java platform allows us to create reusable objects in memory. However, all of those objects exist only as long as the Java virtual machine remains running. It would be nice if the objects we create could exist beyond the lifetime of the virtual machine, wouldn't it? Well, with object serialization, you can flatten your objects and reuse them in powerful ways.
How
An object is marked serializable by implementing the java.io.Serializable interface, which signifies to the underlying API that the object can be flattened into bytes and subsequently inflated in the future. The completely empty Serializable is only a marker interface -- it simply allows the serialization mechanism to verify that the class is able to be persisted.
Rule #1: The object to be persisted must implement the Serializable interface or inherit that implementation from its object hierarchy.
Rule #2: The object to be persisted must mark all nonserializable fields transient
The Default Mechanism
To persist an object in Java, we must have a persistent object. An object is marked serializable by implementing the java.io.Serializable interface, which signifies to the underlying API that the object can be flattened into bytes and subsequently inflated in the future.
Let's look at a persistent class we'll use to demonstrate the serialization mechanism:
10 import java.io.Serializable;
20 import java.util.Date;
30 import java.util.Calendar;
40 public class PersistentTime implements Serializable
50 {
60 private Date time;
70
80 public PersistentTime()
90 {
100 time = Calendar.getInstance().getTime();
110 }
120
130 public Date getTime()
140 {
150 return time;
160 }
170 }
The next step is to actually persist the object. That is done with the java.io.ObjectOutputStream class. That class is a filter stream--it is wrapped around a lower-level byte stream (called a node stream) to handle the serialization protocol for us. Node streams can be used to write to file systems or even across sockets. That means we could easily transfer a flattened object across a network wire and have it be rebuilt on the other side!
Take a look at the code used to save the PersistentTime object:
10 import java.io.ObjectOutputStream;
20 import java.io.FileOutputStream;
30 import java.io.IOException;
40 public class FlattenTime
50 {
60 public static void main(String [] args)
70 {
80 String filename = "time.ser";
90 if(args.length > 0)
100 {
110 filename = args[0];
120 }
130 PersistentTime time = new PersistentTime();
140 FileOutputStream fos = null;
150 ObjectOutputStream out = null;
160 try
170 {
180 fos = new FileOutputStream(filename);
190 out = new ObjectOutputStream(fos);
200 out.writeObject(time);
210 out.close();
220 }
230 catch(IOException ex)
240 {
250 ex.printStackTrace();
260 }
270 }
280 }
The real work happens on line 200 when we call the ObjectOutputStream.writeObject() method, which kicks off the serialization mechanism and the object is flattened (in that case to a file).
To restore the file, we can employ the following code:
10 import java.io.ObjectInputStream;
20 import java.io.FileInputStream;
30 import java.io.IOException;
40 import java.util.Calendar;
50 public class InflateTime
60 {
70 public static void main(String [] args)
80 {
90 String filename = "time.ser";
100 if(args.length > 0)
110 {
120 filename = args[0];
130 }
140 PersistentTime time = null;
150 FileInputStream fis = null;
160 ObjectInputStream in = null;
170 try
180 {
190 fis = new FileInputStream(filename);
200 in = new ObjectInputStream(fis);
210 time = (PersistentTime)in.readObject();
220 in.close();
230 }
240 catch(IOException ex)
250 {
260 ex.printStackTrace();
270 }
280 catch(ClassNotFoundException ex)
290 {
300 ex.printStackTrace();
310 }
320 // print out restored time
330 System.out.println("Flattened time: " + time.getTime());
340 System.out.println();
350 // print out the current time
360 System.out.println("Current time: " + Calendar.getInstance().getTime());
370 }
380 }
Nonserializable Objects
The basic mechanism of Java serialization is simple to use, but there are some more things to know. As mentioned before, only objects marked Serializable can be persisted. The java.lang.Object class does not implement that interface. Therefore, not all the objects in Java can be persisted automatically. The good news is that most of them -- like AWT and Swing GUI components, strings, and arrays -- are serializable.
On the other hand, certain system-level classes such as Thread, OutputStream and its subclasses, and Socket are not serializable. Indeed, it would not make any sense if they were. For example, thread running in my JVM would be using my system's memory. Persisting it and trying to run it in your JVM would make no sense at all. Another important point about java.lang.Object not implementing the Serializable interface is that any class you create that extends only Object (and no other serializable classes) is not serializable unless you implement the interface yourself (as done with the previous example).
That situation presents a problem: what if we have a class that contains an instance of Thread? In that case, can we ever persist objects of that type? The answer is yes, as long as we tell the serialization mechanism our intentions by marking our class's Thread object as transient.
Let's assume we want to create a class that performs an animation. I will not actually provide the animation code here, but here is the class we'll use:
10 import java.io.Serializable;
20 public class PersistentAnimation implements Serializable, Runnable
30 {
40 transient private Thread animator;
50 private int animationSpeed;
60 public PersistentAnimation(int animationSpeed)
70 {
80 this.animationSpeed = animationSpeed;
90 animator = new Thread(this);
100 animator.start();
110 }
120 public void run()
130 {
140 while(true)
150 {
160 // do animation here
170 }
180 }
190 }
Stop That Serialization!
OK, we have seen quite a bit about the serialization process, now let's see some more. What if you create a class whose superclass is serializable but you do not want that new class to be serializable? You cannot unimplement an interface, so if your superclass does implement Serializable, your new class implements it, too (assuming both rules listed above are met). To stop the automatic serialization, you can once again use the private methods to just throw the NotSerializableException. Here is how that would be done:
10 private void writeObject(ObjectOutputStream out) throws IOException
20 {
30 throw new NotSerializableException("Not today!");
40 }
50 private void readObject(ObjectInputStream in) throws IOException
60 {
70 throw new NotSerializableException("Not today!");
80 }
Any attempt to write or read that object will now always result in the exception being thrown. Remember, since those methods are declared private, nobody could modify your code without the source code available to them -- no overriding of those methods would be allowed by Java.
Version Control - serialVersionUID
imagine you create a class, instantiate it, and write it out to an object stream. That flattened object sits in the file system for some time. Meanwhile, you update the class file, perhaps adding a new field. What happens when you try to read in the flattened object?
Well, the bad news is that an exception will be thrown -- specifically, the java.io.InvalidClassException -- because all persistent-capable classes are automatically given a unique identifier. If the identifier of the class does not equal the identifier of the flattened object, the exception will be thrown. However, if you really think about it, why should it be thrown just because I added a field? Couldn't the field just be set to its default value and then written out next time?
Yes, but it takes a little code manipulation. The identifier that is part of all classes is maintained in a field called serialVersionUID. If you wish to control versioning, you simply have to provide the serialVersionUID field manually and ensure it is always the same, no matter what changes you make to the classfile. You can use a utility that comes with the JDK distribution called serialver to see what that code would be by default (it is just the hash code of the object by default).
Here is an example of using serialver with a class called Baz:
serialver Baz
Baz: static final long serialVersionUID = 10275539472837495L;
Simply copy the returned line with the version ID and paste it into your code. (On a Windows box, you can run that utility with the - show option to simplify the copy and paste procedure.) Now, if you make any changes to the Baz class file, just ensure that same version ID is specified and all will be well.
The version control works great as long as the changes are compatible. Compatible changes include adding or removing a method or a field. Incompatible changes include changing an object's hierarchy or removing the implementation of the Serializable interface. A complete list of compatible and incompatible changes is given in the Java Serialization Specification.
Externalizable
create your own protocol with the Externalizable interface. Instead of implementing the Serializable interface, you can implement Externalizable, which contains two methods:
public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
Just override those methods to provide your own protocol. Unlike the previous two serialization variations, nothing is provided for free here, though. That is, the protocol is entirely in your hands. Although it's the more difficult scenario, it's also the most controllable.
An example situation for that alternate type of serialization: read and write PDF files with a Java application. If you know how to write and read PDF (the sequence of bytes required), you could provide the PDF-specific protocol in the writeExternal and readExternal methods.