dcsimg
December 8, 2016
Hot Topics:

Advanced Concepts of Java Object Serialization

  • June 13, 2016
  • By Manoj Debnath
  • Send Email »
  • More Articles »

Serialization literally refers to arranging something in a sequence. It is a process in Java where the state of an object is transformed into a stream of bits. The transformation maintains a sequence in accordance to the metadata supplied, such as a POJO. Perhaps, it is due to this transformation from an abstraction to a raw sequence of bits that it is referred to as serialization by etymology. This article takes up serialization and its related concepts and tries to delineate some of its nooks and crannies, along with their implementation in the Java API.

An Overview

Serialization makes any POJO persistable by converting it into a byte stream. The byte stream then can be stored in a file, memory, or a database.

Serial1
Figure 1: Converting to a byte stream

Therefore, the key idea behind serialization is the concept of a byte stream. A byte stream in Java is an atomic collection of 0s and 1s in a predefined sequence. Atomic means that they are not further derivable. Raw bits are quite flexible and can be transmuted into anything: character, number, Java object, and so forth. Bits individually do not mean anything unless they are produced and consumed by the definition of some meaningful abstraction. In serialization, this meaning is derived from a pre-defined data structure called class and they are instantiated into an active entity called a Java object. The raw bit stream then is stored in a repository such as a file in the file system, array of bytes in the memory, or stored in the database. At a later time, this bit stream can be restored back into its original Java object in a reverse procedure. This reverse process is called deserialization.

Serial2
Figure 2: Serialization

The object serialization and deserialization processes are designed to work recursively. That means, when any object serialized at the top of an inheritance hierarchy, the inherited objects gets serialized. The reference objects are located recursively and serialized. During the restoration, a reverse process is applied and the object is deserialized in a bottom-up fashion.

Serializable Interface

An object to be serialized must implement a java.io.Serializable interface. This interface contains no members and is used to designate a class as serializable. As mentioned earlier, all inherited subclasses are also serialized by default. All the member variables of the designated class are persisted except the members declared as transient and static; they are not persisted. In the following example, class A implements Serializable. Class B inherits class A; as a result, B is also serializable. Class B contains a reference to class C. Class C also must implement Serializable interface; otherwise, java.io.NotSerializableException will be thrown at runtime.

package org.mano.example;

import java.io.Serializable;

public class A implements Serializable {
   private static final long serialVersionUID = 1L;
   public String a;
   public static int STATIC_VAL = 0;
   public transient int TRANSIENT_VAL = 0;

   // ...getters and setters

}

package org.mano.example;

import java.io.Serializable;

public class C implements Serializable {
   private static final long serialVersionUID = 1L;
   private String c;
   // ...getters and setters
}


package org.mano.example;

import java.io.*;

public class B extends A {

   private static final long serialVersionUID = 1L;
   private String b;

   private C refC;

   // ...getters and setters

   public static void main(String[] args)
   throws IOException, ClassNotFoundException {

      String filePath = "/home/test/testfile.sz";
      B b = new B();

      b.setA("Class A");
      b.setB("Class B");

      b.getRefC().setC("Class C");

      b.setTRANSIENT_VAL(100);
      A.setSTATIC_VAL(400);
      B.setSTATIC_VAL(200);

      FileOutputStream fileOut =
         new FileOutputStream(filePath);
      ObjectOutputStream objOut =
         new ObjectOutputStream(fileOut);
      objOut.writeObject(b);

      objOut.flush();
      fileOut.close();


      FileInputStream fileIn =
         new FileInputStream(filePath);
      ObjectInputStream objIn =
         new ObjectInputStream(fileIn);
      B b2 = (B) objIn.readObject();

      objIn.close();
      fileIn.close();

      System.out.println("A=" + b2.getA());
      System.out.println("B=" + b2.getB());

      System.out.println("C=" +
         b2.getRefC().getC());

      System.out.println("Static val=" +
         A.getSTATIC_VAL());
      System.out.println("Static val=" +
         B.getSTATIC_VAL());
      System.out.println("Transient val=" +
         b2.getTRANSIENT_VAL());

   }
}

Output:

A=Class A
B=Class B
C=Class C
Static val=200
Static val=200
Transient val=0

In case you want to use a single object read to or write from a stream, use the readUnshared and writeUnshared methods instead of readObject and writeObject, respectively.

Observe that any changes in the static and transient variables are not stored in the process. There are a number of problem with the serialization process. As we have seen, if a super class is declared serializable, all the sub classes also get serialized. This means, if A inherits B inherits C inherits D... All the objects would be serialized! One way to make fields of these classes non-serializable is to use the transient modifier. What if we have, say, 50 fields that we do not want to persist? We have to declare those 50 fields as transient! Similar problems can arise in the deserialization process. What if we want to deserialize only five fields rather than restore all 10 fields serialized and stored previously?

There is a specific way to stop serialization in the case of inherited classes. The way out is to write your own readObject and writeObject method as follows.

import java.io.*;

public class NonSerializedBook implements Serializable{

   private void readObject(ObjectInputStream in)
   throws IOException, ClassNotFoundException{
      // ...
   }
   private void writeObject(ObjectOutputStream out)
   throws IOException{
      // ...
   }
   // ...
}

A serializable class recommends declaring a unique variable, called serialVersionUID, to identify the data persisted. If this optional variable is not supplied, JVM creates one by an internal logic. This is time consuming.

Note: The JDK bin directory contains a serialver tool. This tool can be used to generate an appropriate value for serialVersionUID. Although Java uses specific logic to generate this number, it literally is quite arbitrary and can be any number. Use serialveras follows:
import java.io.*;

public class Book implements Serializable{

   public static void main(String[] args){

      System.out.println("Hello");

   }

}

Compile to create the class file:

$ javac Book.java
$ serialver Book

The output would be like what's shown in Figure 3.

Serial3
Figure 3: Results of the compiled class file

In a nutshell, a serialization interface needs some change with better control in the serialization and deserialization process.

An externalizable interface provided some improvement. But, bear in mind, the automatic implementation of a serialization process with the Serializable interface is fine in most cases. Externalizable is a complementary interface to allay many of its problems where better control over serialization/deserialization is sought.

Externalizable Interface

The process of serialization and deserialization is pretty straightforward and most of the intricacies to storing and restoring an object are handled automatically. Sometimes, is may happen that the programmer needs some control over persistence process; say, the object to be stored needs to be compressed or encrypted before storing, and similarly, decompression and decryption need to happen during the restoration process. This is where you need to implement the Externalizable interface. The Externalizable interface extends the Serializable interface and provides two member functions to override by the implemented classes.

  • void readExternal(ObjectInput in)
  • void writeExternal(ObjectOutput out)

The readExternal method reads the byte stream from ObjectInput and writeStream writes the byte stream to ObjectOutput. ObjectInput and ObjectOutput are interfaces that extend the DataInput and DataOutput interface, respectively. The polymorphic read and write methods are called to serialize an object.

package org.mano.example;

import java.io.*;
import java.util.Random;

public class Book implements Externalizable {
   private int bookId;
   private String isbn;
   private String title;
   private String publisher;
   private String author;
   // ... constructors and getters and setters

   @Override
   public void writeExternal(ObjectOutput out)
   throws IOException {
      out.writeInt(getBookId());
      out.writeObject(getIsbn());
      out.writeObject(getTitle());
      out.writeObject(getPublisher());
      out.writeObject(getAuthor());
   }

   @Override
   public void readExternal(ObjectInput in)
   throws IOException, ClassNotFoundException {
      setBookId(in.readInt());
      setIsbn(in.readObject().toString());
      setTitle(in.readObject().toString());
      setPublisher(in.readObject().toString());
      setAuthor(in.readObject().toString());
   }




   @Override
   public String toString() {
      return "Book [bookId=" + bookId + ", isbn="
      + isbn + ", title=" + title + ", publisher="
      + publisher + ", author=" + author + "]";
    }

   public static final String FILE_PATH =
      "/home/test/mylib.sz";

   public static void main(String[] args)
   throws IOException, ClassNotFoundException {
      Random rand = new Random();

      Book b1 = new Book(rand.nextInt(100),
      "123-456-789", "Graph Theory", "PHI", "ND");
      FileOutputStream fileOut =
         new FileOutputStream(FILE_PATH);
      ObjectOutputStream objOut =
         new ObjectOutputStream(fileOut);
      b1.writeExternal(objOut);

      objOut.flush();
      fileOut.close();

      FileInputStream fileIn =
         new FileInputStream(FILE_PATH);
      ObjectInputStream objIn =
         new ObjectInputStream(fileIn);
      Book b2 = new Book();
      b2.readExternal(objIn);
      objIn.close();

      System.out.println(b2);
   }
}

Output:

Book [bookId=6, isbn=123-456-789, title=Graph Theory,
   publisher=PHI, author=ND]
Note: Any field declared as transient has no effect on externalization. (Unlike the Serializable interface, it is storable with externalization.)

Externalization makes the serialization and deserialization processes much more flexible and give you better control. But, there are a few points to remember when using Externalizable interface:

  • Classes that implement the Externalizable interface must have a default no-argument constructor.
  • You must override and implement the readExternal and writeExternal methods, explicitly.
  • Every serialization code is defined within the writeExternal method and deserialization code within readExternal.

According to the preceding properties, any non-static inner class is not externalizable. The reason is that the JVM modifies the constructor of the inner classes by adding a reference to the parent class at the time of compilation. As a result, the idea of having a no-argument constructor is simply inapplicable in case of non-static inner classes. Because we can control what field to persist and what not to with the help of the readExternal and writeExternal methods, making a field non-persistable with a transient modifier is also irrelevant.

Conclusion

Serialization and Externalizable is a tagging interface to designate a class for persistence. The instances of these classes may be transformed and stored in byte stream storage. The storage may be a file on disk or database, or even transmitted across a network. The serialization process and Java I/O stream are inseparable. They work together to bring out the essence of object persistence.

References


Tags: Java, Serialization, JDK, Java APIs, POJO, deserialization, Byte Stream, Java Object Serialization, persistable




Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date
Rocket Fuel