Tuesday, February 8, 2011

Serialization in Java

Serialization in Java

Dear reader,
I have discussed here below topics:
1) What is serialization and how it works?
2) Impact of SerialVersionUID in Java files and on serialization behaviour.
3) Impact of serialization and de-serialization on Super class and base classes.
4) How to make a class or package non-serializable as few of classes in Java like "Thread, 
   ServerSocket, Socket, DatagramSocket etc" can't be serialized. The reason these classes
   are tightly coupled with Native OS implementation.

Java serialization has been around since the Java version 1.1 days. Serialization is a key/mechanism
of persisting a Java object for future use, so this persistence can be stored in File or DB anything.
But the storage of Java object is only possible if you convert an object into bits/bytes. 

Hence Serialization is a mechanism to convert a Java object into bit/byte streams so that it can 
be stored in either storage media or sent via network to another machine. Then the recipient or reader 
need to De-Serialize the same bit/bytes pattern and get the actual object.

It is something like Sender is encrypting a Password string and sent over network and then Recipient 
decrypts the same string and fetches the actual value. 


Let’s create a basic class named NonSerializableClass. 
-----------------------
    package com.java.serialize;
    public class NonSerializableClass {
    }
-----------------------

Now let’s try to serialize it. Here’s the code to serialize NonSerializableClass.
-----------------------
import java.io.*;
public class NonSerializableTest {
    public static void main(String[] args) {
            NonSerializableClass nonSerializableObj = new NonSerializableClass();            
            try {
                ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("HoldSerial.txt"));                
                out.writeObject(nonSerializableObj);
                out.close();                
            }
            catch(Exception e) {
                e.printStackTrace();
            }
    }
}
-----------------------
On running the program we encounter the following exception.
    Not Serializable Class
    java.io.NotSerializableException: com.java.serialize.NonSerializableClass

Serialization rule number 1:
The first rule of serialization is Not all classes are serializable.
For a class to be serialized it should implement Serializable interface. 

Here’s an example of a Serializable class.
-----------------------
    public class SerializableClass implements Serializable {
       private static final long serialVersionUID = 123L;
       public SerializableClass() {
        System.out.println("Inside SerializableClass constructor");
       }
    }
-----------------------

Now let’s try to serialize this. Here’s the serialization code.
-----------------------
    public class SerializableTest {
        public static void main(String[] args) {
            SerializableClass serObj = new SerializableClass();
            try {
                File f = new File("C:\\obj.ser"); //File name can be any thing.
                f.createNewFile();
                System.out.println("Serializing Object");
                FileOutputStream out = new FileOutputStream(f); //Breaking one liner output stream into buffer.
                BufferedOutputStream buf = new BufferedOutputStream(out);
                ObjectOutputStream objOut = new ObjectOutputStream(buf);
                objOut.writeObject(serObj);
    
                objOut.close();
                buf.close();
                out.close();
                System.out.println("Serialization complete.");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
-----------------------
Serialization works fine without throwing any exceptions. The output of the class run is
    Inside SerializableClass constructor
    Serializing Object
    Serialization complete.

Serialization rule number 2:
Only Classes implementing Serializable interface support serialization.
OK we were able to serialize the object, now let’s try to deserialize it. 

Here’s the source code...
-----------------------
    public class DeSerializeObjectTest {
        public static void main(String[] args) {
            try {
                File f = new File("C:\\obj.ser");     
                System.out.println("Deserializing Object");
                FileInputStream input = new FileInputStream(f);
                BufferedInputStream buf = new BufferedInputStream(input);
                ObjectInputStream objip = new ObjectInputStream(buf);
                Object obj = objip.readObject();
     
                SerializableClass ser = (SerializableClass)obj; 
                objip.close();
                buf.close();
                input.close();
     
                System.out.println("Deserialization complete.");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
-----------------------
The output is 
    Deserializing Object
    Deserialization complete.  //Note: Constructor didn't get invoked while deserializing.

So far so good, now let’s add an attribute to the class in question. 

Here’s the new source...
-----------------------
    public class SerializableWithParamClass implements Serializable {
        private static final long serialVersionUID = 137L;
        private String name = null;
        public SerializableWithParamClass() {
            System.out.println("Inside SerializableWithParamClass constructor.");
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
-----------------------
Here’s the test class code...
    public class SerializableWithParamTest {
       public static void main(String[] args) {
             SerializableWithParamClass param = new SerializableWithParamClass();
             param.setName("Testing.......");
             System.out.println("Begining the serialization process.");
             File f = new File("C:\\paramObj.ser");
             try {   
                f.createNewFile();
                System.out.println("Serializing Object");
    
                FileOutputStream out = new FileOutputStream(f);
                BufferedOutputStream buf = new BufferedOutputStream(out);
                ObjectOutputStream objOut = new ObjectOutputStream(buf);
                objOut.writeObject(param);
     
                objOut.close();
                buf.close();
                out.close();
                System.out.println("Serialization complete.");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("Ending the serialization process.");
            System.out.println("Begining the deserialization process.");
            try {   
                System.out.println("Deserializing Object");
                BufferedInputStream buf = new BufferedInputStream(new FileInputStream(f));
                ObjectInputStream objip = new ObjectInputStream(buf);
                Object obj = objip.readObject();
     
                SerializableWithParamClass ser = (SerializableWithParamClass)obj;
                System.out.println("Name: " + ser.getName());
                objip.close();
                buf.close();                
                System.out.println("Deserialization complete.");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            System.out.println("Ending the deserialization process.");
        }
    }
-----------------------
The output is as follows:
01    Inside SerializableWithParamClass constructor.
02    Begining the serialization process.
03    Serializing Object
04    Serialization complete.
05    Ending the serialization process.
06    Begining the deserialization process.
07    Deserializing Object
08    Name: Testing.......
09    Deserialization complete.
10    Ending the deserialization process.

I am sure you have noticed by now the attribute serialVersionUID. What’s the need for having this attribute? 
Whenever object is serialized Java stores the java major and minor version information. Along with this it 
also needs to store some kind of version information, something which says that I am version 1.0 of this class, 
on addition/deletion of an attribute or method it should say that I am version 2.0. The serialVersionUID 
serves this purpose. The developer can define a hard coded value as I have done or allow Java can produce 
the value at runtime which would be based on the evaluation of the class.

The reason to discuss: Suppose you have serialized your objects into a File and keep using after de-serialization.
       After 2 years, you have changed your original source code and added/deleted some methods, attributes etc
       and now serialized your objects again but keeping the older stored/serialized objects as it is with client. 
       Now if your old serialized objects need to de-serialize with new new code base, it won't work. 
       It will say java.io.InvalidClassException. I mean Version/SerialVersionUID is modified.

So what affects the value of serialVersionUID and what does not?
What affects SerialVersionUID change?
1.    Non-default Constructors
2.    Addition/Deletion of non-private static or instance methods as well as their access modifiers
3.    Addition/Deletion of static/instance attributes as well as their access modifiers
4.    The interfaces implemented by the class

What does not affect SerialVersionUID?
1.    Default Constructor
2.    Addition of private static or instance methods
3.    The class extended by the class to be serialized

One approach is to define a fixed value for serialVersionUID as I have done. The benefit is that if we 
have serialized an object and then added new attribute/method, as the serialVersionUID has not changed, 
the deserialization process is not hindered. Java force such old serialized objects to de-serialize 
using new source code if Version is same.

Let’s serialize an object of SerializableClass and then change the serialVersionUID from 123L to 124L
and then compile it.

Now on deserializing the old object we encounter the following exception:
1    Deserializing Object
2    java.io.InvalidClassException: com.java.serialize.SerializableClass; local class incompatible: 
stream classdesc serialVersionUID = 123, local class serialVersionUID = 124


Now let’s examine the impact of serialization on object hierarchy.
-------------------------     
    public class A {
       private String name = null;
       public A() {
           System.out.println("Inside A's constructor.");
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
-------------------------     
    public class B extends A implements Serializable {
        private String detail = null;
        public B() {
            System.out.println("Inside B's constructor.");
        }
        public String getDetail() {
            return detail;
        }
        public void setDetail(String detail) {
           this.detail = detail;
        }
    }

Now let’s serialize and deserialize it. Here’s the code...
------------------------- 
    public class InheritTest {
        public static void main(String[] args) {
            B b = new B();
            b.setName("Test");
            b.setDetail("Test Details");
            System.out.println("Begining the serialization process.");
            File f = new File("C:\\inheritObj.ser");
            try {   
                f.createNewFile();
                System.out.println("Serializing Object");
     
                FileOutputStream out = new FileOutputStream(f);
                BufferedOutputStream buf = new BufferedOutputStream(out);
                ObjectOutputStream objOut = new ObjectOutputStream(buf);
                objOut.writeObject(b);
                objOut.close();
                buf.close();
                out.close();
                  System.out.println("Serialization complete.");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("Ending the serialization process.");
            System.out.println("Begining the deserialization process.");
            try {   
                System.out.println("Deserializing Object");
                FileInputStream input = new FileInputStream(f);
                BufferedInputStream buf = new BufferedInputStream(input);
                ObjectInputStream objip = new ObjectInputStream(buf);
                Object obj = objip.readObject();
     
                B bSer = (B)obj;
                System.out.println("Name: " + bSer.getName());
                System.out.println("Detail: " + bSer.getDetail());
                objip.close();
                buf.close();
                input.close();
                System.out.println("Deserialization complete.");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            System.out.println("Ending the deserialization process.");
        }
    }
------------------------- 
The output generated is:
01    Inside A's constructor.
02    Inside B's constructor.
03    Begining the serialization process.
04    Serializing Object
05    Serialization complete.
06    Ending the serialization process.
07    Begining the deserialization process.
08    Deserializing Object
09    Inside A's constructor.
10    Name: null
11    Detail: Test Details
12    Deserialization complete.
13    Ending the deserialization process.

Note that during deserialization, the constructor of A is invoked and the attribute value of name is null.
Now let’s try two options. Make A implement Serializable and let B continue to implement Serializable. 
-------------------------     
    public class A implements Serializable{
        private String name = null;
        public A() {
            System.out.println("Inside A's constructor.");
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
-------------------------     
    public class B extends A implements Serializable {
        private String detail = null;
        public B() {
            System.out.println("Inside B's constructor.");
        }
        public String getDetail() {
            return detail;
        }
        public void setDetail(String detail) {
            this.detail = detail;
        }
    }
-------------------------     
The output is 
01    Inside A's constructor.
02    Inside B's constructor.
03    Begining the serialization process.
04    Serializing Object
05    Serialization complete.
06    Ending the serialization process.
07    Begining the deserialization process.
08    Deserializing Object
09    Name: Test
10    Detail: Test Details
11    Deserialization complete.
12    Ending the deserialization process.

It is in line with our expectations. Super class constructor didn't invoke.
Now option two where A implements Serializable and B does not implement Serializable.
-------------------------     
    public class A implements Serializable{
        private String name = null;
        public A() {
            System.out.println("Inside A's constructor.");
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
-------------------------     
    public class B extends A {
        private String detail = null;
        public B() {
            System.out.println("Inside B's constructor.");
        }
        public String getDetail() {
            return detail;
        }
        public void setDetail(String detail) {
            this.detail = detail;
        }
    }
-------------------------     
The output is
01    Inside A's constructor.
02    Inside B's constructor.
03    Begining the serialization process.
04    Serializing Object
05    Serialization complete.
06    Ending the serialization process.
07    Begining the deserialization process.
08    Deserializing Object
09    Name: Test
10    Detail: Test Details
11    Deserialization complete.
12    Ending the deserialization process.

The output remains unchanged. Super class constructor didn't invoke. 

Java Serialization rule no 3:
In Object hierarchy the root class needs to implement Serializable interface to ensure serializable 
of all attributes within the object hierarchy.
-------------------------

At Last I am discussing how to avoid making a class serializable. Note: If your super class
doesn't implement Serializable, it can't be serialized but if a sub-class implements, then the whole object
hierarchy can be serialized again.

To avoid this we need to re-define "writeObject() and readObject()" methods of ObjectOutputStream and 
ObjectInputStream. I am writing re-define because these methods are declared as final, so can't be overridden.

Below is the complete code:
--------------------
//Super class doesn't implement Serializable here but sub class does.
public class SuperClass {
    public SuperClass(){
        System.out.println("Super class constructor called");
    }
}
---------------
//Sub class
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class BaseClass extends SuperClass implements Serializable {
    public String name;
    public BaseClass(){
        System.out.println("Base class constructor called");
        name="Deepak modi";
    }
    public static void main(String[] args) throws IOException,ClassNotFoundException {
        BaseClass b=new BaseClass();
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("HoldSerial.txt"));
        out.writeObject(b);
        System.out.println("Base class object serialized/stored in a file");

        ObjectInputStream in=new ObjectInputStream(new FileInputStream("HoldSerial.txt"));
        BaseClass bIn=(BaseClass)in.readObject();
        System.out.println("Base class object de-serialized/read from file");
        System.out.println("Object: "+bIn);
        System.out.println("Object name value: "+bIn.name);
    }
    //Re-defining the method
    private void readObject(ObjectInputStream o) throws IOException, ClassNotFoundException {  
        System.out.println("These objects can't be de-serialized");
        throw new IOException("These objects can't be de-serialized");
    }  
    private void writeObject(ObjectOutputStream o) throws IOException {  
        System.out.println("These objects can't be serialized");
        throw new IOException("These objects can't be serialized");
    }     
    public String toString() {  
        return name;  
    }  
}

//Output when you run this main method:
Super class constructor called
Base class constructor called
These objects can't be serialized
Exception in thread "main" java.io.IOException: These objects can't be serialized
    at BaseClass.writeObject(BaseClass.java:33)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:917)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1339)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1290)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1079)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:302)
    at BaseClass.main(BaseClass.java:17)


//You uncomment the "readObject() and "writeObject()" and then see behavior:
I did that and here is the output:::::::::::

Super class constructor called
Base class constructor called
Base class object serialized/stored in a file
Super class constructor called
Base class object de-serialized/read from file
Object: Deepak modi
Object name value: Deepak modi

------------------------END-----------------------

1 comment: