Serialization

Serialization is best described by its absence. In Section the section called “The TCP Client/Server”, we passed String objects from the client to the server and back again and we were not concerned with the transport system.

That is a correct approach, however, things get a little bit more difficult when we want to send objects that consist of a number of states (i.e. is-a-part-of values). Before the advent of serialization we (as the programmer) were required to break an object into its component parts and send them one-by-one over the stream, where they would then be re-constructed one-by-one. So for example, if an object Fish contained a name and a type String states, these would have to be written one at a time to the stream using a method like writeUTF(). The server would then have to read these String objects one at a time and re-construct the Fish object. However, this is prone to error; Suppose the order of string reading was reversed on the server, then the fish would have the name of type and the type of name.

Object serialization makes this task straightforward, as we simply add to the code the fact that the Fish class implements Serializable and almost everything else is taken care of.

The serialization mechanism used is capable of handling a wide variety of situations. When you serialize an object you save all the states of the object. This even includes states marked private. However, there are times when you don't want a state to be persistent, especially if the object is tied to resources that are specific to this session of the VM. It does not make any sense to serialize the object for later use. Fortunately, the Java programming language includes the declaration transient. A state marked transient means that the the state is not saved when an object is serialized. Static data is also not serializable, and so is ignored.

If the private states in your class include sensitive data, e.g. credit card numbers, passwords etc. you should not serialize them as this data can be plainly read in the stream. The simplest way to remove this data is to use private transient.

  import java.io.*;			// contains the Serializable interface

  public class Fish implements Serializable
  {
	private String name;
	private String type;

	public Fish(String name, String type)
	{
		this.name = name;
		this.type = type;
	}

	public String getName() { return this.name;	}

	public String getType() { return this.type;	}
  }

The Fish class implements the Serializable interface, which allows the data of the class to be ordered in a serial form, to send it across a serial network (of sorts). The Serializable interface is simple to implement, as we do not have to write any additional code. The Serializable interface does not have any methods, as it is simply a "signal" interface that indicates to the JVM that you want to use the default serialization mechanism.

On the client-side we can create a fish object (say myFish) and send it using the send(Object o) method discussed previously. In the HandleConnection class we can read in each object, check to see what class it is, and then cast convert the object to a Fish.

So, for example:


  if (o.getClass().getName().equals("Fish"))
  {
	Fish aFish = (Fish) o;
  }
  

Once we have converted to the Fish object we can simply call the usual methods:

  System.out.println("\nFish name is: "+ aFish.getName());
  System.out.println("\nFish type is: "+ aFish.getType());
 

Once an object is written to a stream, its state is fixed. In effect a copy of the object is created on the client side and sent to the server side. If this object changes state on the client side, while the server is busy processing the copy, then the copy is no longer up-to-date. This can be problematic. One solution is to lock the client object on the client side, so that it cannot be altered while the copied object is in transit to or from the server - but what happens if the server or communication fails?

The use of serialization also allows easy persistant storage through the use of files. For example if we wished to store the Fish objects to a file we could use:


    FileOutputStream out = new FileOutputStream("Storage.file");
    ObjectOutput s = new ObjectOutputStream(out);
    s.writeObject("Some example string");		// store an example String
    s.writeObject(new Fish("Jim", "Guppy"));	// store an example Fish
    s.flush();

That allows us to store a String object or a Fish object using the same writeObject() method. To read in from this file Storage.file we could use:


    FileInputStream in = new FileInputStream("Storage.file");
    ObjectInputStream s = new ObjectInputStream(in);
    String theString = (String) s.readObject();
    Fish myFish = (Fish) s.readObject();
    

As the communication becomes more complex, the serialization task becomes even more difficult. Serialization, although providing a huge advantage over manually streaming data, still requires the programmer to maintain the protocol for communication, distributing the protocols and making sure that both the client and server are aware of the serialized object class (in this case Fish), allowing the casting to take place.

Exercise

Task: Modify the code from the section called “The TCP Client/Server” to send a Fish object across the network and display it on the server side. Use these code samples (from the section called “The TCP Client/Server”):