Friday 24 March 2017

Serialization - Java Tutorials

Serialization is the process of writing the state of an object to a byte stream. This is useful when you want to save the state of your program to a persistent storage area, such as a file. At a later time, you may restore these objects by using the process of deserialization.

Serialization is also needed to implement Remote Method Invocation (RMI). RMI allows a Java object on one machine to invoke a method of a Java object on a different machine. An object may be supplied as an argument to that remote method. The sending machine serializes the object and transmits it. The receiving machine deserializes it.

Assume that an object to be serialized has references to other objects, which, in turn, have references to still more objects. This set of objects and the relationships among them form a directed graph. There may also be circular references within this object graph. That is, object X may contain a reference to object Y, and object Y may contain a reference back to object X. Objects may also contain references to themselves. The object serialization and deserialization facilities have been designed to work correctly in these scenarios. If you attempt to serialize an object at the top of an object graph, all of the other referenced objects are recursively located and serialized.

Similarly, during the process of deserialization, all of these objects and their references are correctly restored.

An overview of the interfaces and classes that support serialization follows.


Serializable

Only an object that implements the Serializable interface can be saved and restored by the serialization facilities. The Serializable interface defines no members. It is simply used to indicate that a class may be serialized. If a class is serializable, all of its subclasses are also serializable.

Variables that are declared as transient are not saved by the serialization facilities. Also, static variables are not saved.


Externalizable

The Java facilities for serialization and deserialization have been designed so that much of the work to save and restore the state of an object occurs automatically. However, there are cases in which the programmer may need to have control over these processes. For example, it may be desirable to use compression or encryption techniques. The Externalizable interface is designed for these situations.

The Externalizable interface defines these two methods:

      void readExternal(ObjectInput inStream)
          throws IOException, ClassNotFoundException
  
      void writeExternal(ObjectOutput outStream)
          throws IOException

In these methods, inStream is the byte stream from which the object is to be read, and outStream is the byte stream to which the object is to be written.


ObjectOutput

The ObjectOutput interface extends the DataOutput interface and supports object serialization. Note especially the writeObject( ) method. This is called to serialize an object. All of these methods will throw an IOException on error conditions.


The Methods Defined by ObjectOutput

void close( ):  Closes the invoking stream. Further write attempts will generate an IOException.

void flush( ):  Finalizes the output state so that any buffers are cleared. That is, it flushes the output buffers.

void write(byte buffer[ ]):  Writes an array of bytes to the invoking stream.

void write(byte buffer[ ], int offset, int numBytes):  Writes a subrange of numBytes bytes from the array buffer, beginning at buffer[offset].

void write(int b):  Writes a single byte to the invoking stream. The byte written is the low-order byte of b.

void writeObject(Object obj):  Writes object obj to the invoking stream.


ObjectOutputStream

The ObjectOutputStream class extends the OutputStream class and implements the ObjectOutput interface. It is responsible for writing objects to a stream. A constructor of this class is

      ObjectOutputStream(OutputStream outStream) throws IOException

The argument outStream is the output stream to which serialized objects will be written. They will throw an IOException on error conditions. Java 2 added an inner class to ObjectOuputStream called PutField. It facilitates the writing of persistent fields and its use is beyond the scope of this book.


Commonly Used Methods Defined by ObjectOutputStream

void close( ):  Closes the invoking stream. Further write attempts will generate an IOException.

void flush( ):  Finalizes the output state so that any buffers are cleared. That is, it flushes the output buffers.

void write(byte buffer[ ]):  Writes an array of bytes to the invoking stream.

void write(byte buffer[ ], int offset, int numBytes):  Writes a subrange of numBytes bytes from the array buffer, beginning at buffer[offset].

void write(int b):  Writes a single byte to the invoking stream. The byte written is the low-order byte of b.

void writeBoolean(boolean b):  Writes a boolean to the invoking stream.

void writeByte(int b):    Writes a byte to the invoking stream. The byte written is the low-order byte of b.

void writeBytes(String str):  Writes the bytes representing str to the invoking stream.

void writeChar(int c):  Writes a char to the invoking stream.

void writeChars(String str):  Writes the characters in str to the invoking stream.

void writeDouble(double d):  Writes a double to the invoking stream.

void writeFloat(float f ):  Writes a float to the invoking stream.

void writeInt(int i):  Writes an int to the invoking stream.

void writeLong(long l):  Writes a long to the invoking stream.

final void writeObject(Object obj):  Writes obj to the invoking stream.

void writeShort(int i):  Writes a short to the invoking stream.


ObjectInput

It supports object serialization. Note especially the readObject( ) method. This is called to deserialize an object. All of these methods will throw an IOException on error conditions.


The Methods Defined by ObjectInput

int available( ):  Returns the number of bytes that are now available in the input buffer.

void close( ):  Closes the invoking stream. Further read attempts will generate an IOException.

int read( ):  Returns an integer representation of the next available byte of input. –1 is returned when the end of the file is encountered.

int read(byte buffer[ ]):  Attempts to read up to buffer.length bytes into buffer, returning the number of bytes that were successfully read. –1 is returned when the end of the file is encountered.

int read(byte buffer[ ], int offset, int numBytes):  Attempts to read up to numBytes bytes into buffer starting at buffer[offset], returning the number of bytes that were successfully read. –1 is returned when the end of the file is encountered.

Object readObject( ):  Reads an object from the invoking stream.

long skip(long numBytes):  Ignores (that is, skips) numBytes bytes in the invoking stream, returning the number of bytes actually ignored.


ObjectInputStream

The ObjectInputStream class extends the InputStream class and implements the ObjectInput interface. ObjectInputStream is responsible for reading objects from a stream. A constructor of this class is

      ObjectInputStream(InputStream inStream)
          throws IOException, StreamCorruptedException

The argument inStream is the input stream from which serialized objects should be read.

The most commonly used methods in this class are shown in Table 17-8. They will throw an IOException on error conditions. Java 2 added an inner class to ObjectInputStream called GetField. It facilitates the reading of persistent fields and its use is beyond the scope of this book. Also, the method readLine( ) was deprecated by Java 2 and should no longer be used.


Commonly Used Methods Defined by ObjectInputStream

int available( ):  Returns the number of bytes that are now available in the input buffer.

void close( ):  Closes the invoking stream. Further read attempts will generate an IOException.

int read( ):  Returns an integer representation of the next available byte of input. –1 is returned when the end of the file is encountered.

int read(byte buffer[ ], int offset, int numBytes):  Attempts to read up to numBytes bytes into buffer starting at buffer[offset], returning the number of bytes successfully read. –1 is returned when the end of the file is encountered.

boolean readBoolean( ):  Reads and returns a boolean from the invoking stream.

byte readByte( ):  Reads and returns a byte from the invoking stream.

char readChar( ):  Reads and returns a char from the invoking stream.

double readDouble( ):  Reads and returns a double from the invoking stream.

float readFloat( ):  Reads and returns a float from the invoking stream.

void readFully(byte buffer[ ]):  Reads buffer.length bytes into buffer. Returns only when all bytes have been read.

void readFully(byte buffer[ ], int offset, int numBytes):  Reads numBytes bytes into buffer starting at buffer[offset]. Returns only when numBytes have been read.

int readInt( ):  Reads and returns an int from the invoking stream.

long readLong( ):  Reads and returns a long from the invoking stream.

final Object readObject( ):  Reads and returns an object from the invoking stream.

short readShort( ):  Reads and returns a short from the invoking stream.

int readUnsignedByte( ):  Reads and returns an unsigned byte from the invoking stream.

int readUnsignedShort( ):  Reads an unsigned short from the invoking stream.


A Serialization Example

The following program illustrates how to use object serialization and deserialization. It begins by instantiating an object of class MyClass. This object has three instance variables that are of types String, int, and double. This is the information we want to save and restore.

A FileOutputStream is created that refers to a file named “serial,” and an ObjectOutputStream is created for that file stream. The writeObject( ) method of ObjectOutputStream is then used to serialize our object. The object output stream is flushed and closed.

A FileInputStream is then created that refers to the file named “serial,” and an ObjectInputStream is created for that file stream. The readObject( ) method of ObjectInputStream is then used to deserialize our object. The object input stream is then closed.

Note that MyClass is defined to implement the Serializable interface. If this is not done, a NotSerializableException is thrown. Try experimenting with this program by declaring some of the MyClass instance variables to be transient. That data is then not saved during serialization.

  import java.io.*;

  public class SerializationDemo {
    public static void main(String args[]) {

      // Object serialization
      try {
        MyClass object1 = new MyClass("Hello", -7, 2.7e10);
        System.out.println("object1: " + object1);
        FileOutputStream fos = new FileOutputStream("serial");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(object1);
        oos.flush();
        oos.close();
      } 
      catch(Exception e) {
        System.out.println("Exception during serialization: " + e);
        System.exit(0);
      }

      // Object deserialization
      try {
        MyClass object2;
        FileInputStream fis = new FileInputStream("serial");
        ObjectInputStream ois = new ObjectInputStream(fis);
        object2 = (MyClass)ois.readObject();
        ois.close();
        System.out.println("object2: " + object2);
      }
      catch(Exception e) {
        System.out.println("Exception during deserialization: 
                           " + e);
        System.exit(0);
      }
    }
  }

  class MyClass implements Serializable {
    String s;
    int i;
    double d;
    public MyClass(String s, int i, double d) {
      this.s = s;
      this.i = i;
      this.d = d;
    }
    public String toString() {
      return "s=" + s + "; i=" + i + "; d=" + d;
    }
  }

This program demonstrates that the instance variables of object1 and object2 are identical. The output is shown here:

  object1: s=Hello; i=-7; d=2.7E10
  object2: s=Hello; i=-7; d=2.7E10


Stream Benefits

The streaming interface to I/O in Java provides a clean abstraction for a complex and often cumbersome task. The composition of the filtered stream classes allows you to dynamically build the custom streaming interface to suit your data transfer requirements. Java programs written to adhere to the abstract, high-level InputStream, OutputStream, Reader, and Writer classes will function properly in the future even when new and improved concrete stream classes are invented. As you will see in the next chapter, this model works very well when we switch from a file system-based set of streams to the network and socket streams. Finally, serialization of objects is expected to play an increasingly important role in Java programming in the future. Java’s serialization I/O classes provide a portable solution to this sometimes tricky task.

No comments:

Post a Comment