Forum Controls
Spotlight Features

The Rich Engineering Heritage Behind Dependency Injection

Andrew McVeigh takes us on a tour of the rich heritage behind dependency injection, what it represents, and tells us why its here to stay.

NetBeans 6: Matisse Updates

NetBeans 6 delivers great updates to the Matisse GUI builder. Spend a few minutes with Roman Strobl and get an expert briefing on what's new and what has changed.

Introduction to Groovy Part 3

In this, the third and final installation of Andres' Introduction to Groovy series, you learn about how Groovy handles variable numbers of arguments, named parameters, currying, and more about Groovy operators. Including, some new operators.

Easier Custom Components with Swing Fuse

Swing Fuse (actually just Fuse), is a framework designed to make it easier to create your own custom desktop components. In this article, Daniel Spiewak shows you how to get started and provides sample source code you can download.

Benchmark Analysis: Guice vs Spring

Willam Louth shows how he uses JXInsight Probes to investigate probable performance issues with code bases that he is not familiar with. He also highlights possible pitfalls in creating a benchmark, as well as in the analysis of results.
Replies: 2 - Pages: 1  
Threads: [ Previous | Next ]
  Click to reply to this thread Reply

IO: Custom Reading and Writing with Serializable

At 2:04 AM on Nov 27, 2006, R.J. Lorimer wrote:

In the past I worked on an application that was heavily reliant on serialization, and more importantly, customized serialization. To do this, we used readObject(...) and writeObject(...) .

The serialization documentation gives a quick once over of the implementation of these methods - here is a snippet:

Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:
 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;

Implementing these two methods is deceptively simple. Here is a full example class with a self-running main method:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
 
public class Person implements Serializable {
 
	private int age;
	private String firstName;
	private String lastName;
	private Collection<Person> children;
	private Person parent;
	
	public Person(String fname, String lname, int age) {
		if(fname == null) { throw new IllegalArgumentException("First name can't be null."); }
		if(lname == null) { throw new IllegalArgumentException("First name can't be null."); }
		this.firstName = fname;
		this.lastName = lname;
		this.age = age;
	}
	
	public int getAge() {
		return age;
	}
	public String getFirstName() {
		return firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public Collection<Person> getChildren() {
		return children;
	}
	public Person getParent() {
		return parent;
	}
	public void setChildren(Collection<Person> children) {
		this.children = children;
	}
	public void setChildren(Person... children) {
		setChildren( 
			children == null ? 
			new ArrayList<Person>() : 
			new ArrayList<Person>(Arrays.asList(children))
		);		
	}
	public void setParent(Person parent) {
		this.parent = parent;
	}
	
	@Override
	public String toString() {
		return firstName + " " + lastName + " - " + age + "yrs.";
	}
	
	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		age = in.readInt();
		firstName = in.readUTF();
		lastName = in.readUTF();
		children = (Collection<Person>)in.readObject();
		parent = (Person)in.readObject();
		
	}
	private void writeObject(ObjectOutputStream out) throws IOException {
		out.writeInt(age);
		out.writeUTF(firstName);
		out.writeUTF(lastName);
		out.writeObject(children);
		out.writeObject(parent);		
	}
	
	public static void main(String[] args) throws Exception {
		
		Person grandma = new Person("Grandma", "Bear",50);
		Person mother = new Person("Mother", "Bear", 25);
		Person baby = new Person("Baby", "Bear", 2);
		Person toddler = new Person("Toddler", "Bear", 5);
		baby.setParent(mother);
		toddler.setParent(mother);
		mother.setParent(grandma);
		grandma.setChildren(mother);
		mother.setChildren(baby, toddler);
		
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(bos);
		oos.writeObject(grandma);
		oos.flush();
		oos.close();
		bos.close();
		byte [] data = bos.toByteArray();
		ObjectInputStream iis = new ObjectInputStream(new ByteArrayInputStream(data));
		grandma = (Person)iis.readObject();
		System.out.println("Grandma: " + grandma);
		for(Person child : grandma.getChildren()) {
			System.out.println("\tChild:" + child + " - Parent: " + child.getParent());
			for(Person grandChild : child.getChildren()) {
				System.out.println("\t\tGrand Child: " + grandChild + " - Parent: " + grandChild.getParent());
			}
		}
		iis.close();
	}
	
}
 

As you can see from the example, implementing the read and write methods simply involves having a signature you follow on the way in and the way out. Unfortunately, this is an ideal example; it doesn't consider the evolution of classes or what happens with super-classes and sub-classes.

Inheritence

One of the interesting things about customized write/read serialization in Java is that unlike most methods (where you have to call super before or after your own work), the 'readObject()' and 'writeObject()' methods are expected to only deal with the class they are implemented in. Here is an example:

public class SerializableSuper implements Serializable {
 
	private String fieldA;
	
	public SerializableSuper(String fieldA) {
		this.fieldA = fieldA;
	}
	
 
	public void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
		System.out.println("Reading SerializableSuper");
		fieldA = in.readUTF();
	}
	public void writeObject(ObjectOutputStream out) throws IOException {
		System.out.println("Writing SerializableSuper");
		out.writeUTF(fieldA);
	}
	
	
	@Override
	public String toString() {
		return "A: " + fieldA;
	}
}
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
 
public class SerializableSub extends SerializableSuper {
 
	private String fieldB;
	
	public SerializableSub(String fieldA, String fieldB) {
		super(fieldA);
		this.fieldB = fieldB;
	}
	
	@Override
	public String toString() {
		return super.toString() + ", B: " + fieldB;
	}
	
	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
		System.out.println("Reading SerializableSub");
		fieldB = in.readUTF();
	}
	private void writeObject(ObjectOutputStream out) throws IOException {
		System.out.println("Writing SerializableSub");
		out.writeUTF(fieldB);
	}
	
	public static void main(String[] args) throws Exception {
		SerializableSub sub = new SerializableSub("This is Field A", "this is Field B");
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(bos);
		oos.writeObject(sub);
		oos.flush();
		oos.close();
		bos.close();
		byte [] data = bos.toByteArray();
		ObjectInputStream iis = new ObjectInputStream(new ByteArrayInputStream(data));
		sub = (SerializableSub)iis.readObject();
		System.out.println(sub.toString());
	}
}
 

This will output this to the console:

Writing SerializableSuper
Writing SerializableSub
Reading SerializableSuper
Reading SerializableSub
A: This is Field A, B: this is Field B

Class Evolution

One of the main uses of readObject and writeObject is to handle class evolution, where a new version of class needs to be deserialized from an older version. There are several ways to handle this. The first step is that you have to declare an unchanging 'serialVersionUID'. This version number is going to be checked by Java, and if you don't provide it, Java will auto-generate it based on the structure of your class. If the number it has (whether it came from your declaration, or from the generation) differs from the value in the persistence data, it will throw an InvalidClassException, and you are out of luck.

Once you declare the version id, one of the most simple solutions to providing compatible versions is to serialize a personally-managed version along with the class, and use that for managing the evolution. The key here is that whenever you perform a serialization-breaking change, you advance that version number so you can respond to it accordingly in the writeObject method:

private static final long serialVersionUID = 1L;
private static final int CLASS_VERSION = 3;
 
[...]
private void writeObject(ObjectOutputStream out) throws IOException {
	out.writeInt(CLASS_VERSION);
	[...]
}
 
private void readObject(ObjectInputStream in) throws IOException {
	int version = in.readInt();
	switch(version) {
		case 1:
			[...]
		case 2:
			[...]
	}
	[...]
}
 

As you can see in the example above, I just used some constant 'CLASS_VERSION'. It is key you don't confuse this with the 'serialVersionUID' long you can declare, as that number *shouldn't* change, unless you are intending to prevent certain classes from deserializing.

What about Externalizable?

There is another interface you can implement in Java aside from Serializable that fits a similar purpose. java.io.Externalizable extends java.io.Serializable , and specifies two very similar methods to writeObject and readObject - writeExternal(ObjectOutput) and readExternal(ObjectInput) . There are some key differences between Externalizable and Serializable, however:

  • Externalizable classes must explicitly handle super-classes (by calling super.readExternal as an example) whereas serializable super-classes are handled automatically by each class being consulted individually in the write process.
  • Unlike serialization which can serialize and de-serialize irrespective of the available constructors, Externalizable classes must provide a public no-arg constructor.
  • Externalizable is intended to be available to external clients, such as a persistence framework, while the Serializable system is a 'black box' provided by the Java library.

Externalizable has different pros and cons that serializable; what you choose is largely up to your needs and requirements. Many Java developers say the explicit management of super-class persistence required by Externalizable is flawed and is typically too difficult to implement reliably in the real world, and as such avoid it. In addition, some of the main benefits of Externalizable (such as the public availability of the API) aren't realized because the community hasn't latched on to it. Most developers who want a pluggable persistence implementation for their objects also expect some degree of transparency or external implementation (rather than an implementation embedded in the object itself).

1 . At 5:52 AM on Nov 27, 2006, Mikael Grev DeveloperZone Top 100 wrote:
  Click to reply to this thread Reply

Re: IO: Custom Reading and Writing with Serializable

Hello R.J.,

Have a look at this that I wrote some time ago:

http://www.javalobby.org/articles/serialization/?source=archives

It is basically using the built in XMLEncoder/decoder persistence to save the Serializable/Externalizable

We have been using this for all MiG Calendar's persistable classes (50 or so) for quite a while now (since the article was written) though in a slighty enhanced version. It is working really really great and we have absolutely no problem with versioning!

And IMO, if you code against Serializable/Externelizable today, other than for legacy/compatibility reasons, you could probably do better using some other form of persistence mechanism alltogether.. I personally like the 1.4+ XMLEncoder/Decoder.

Cheers,
Mikael Grev (grev at miginfocom dot com)
MiG Java Calendar Component, MiG Layout for Swing/SWT (Vote -> JDK)
2 . At 7:36 AM on Nov 27, 2006, Thomas Hawtin wrote:
  Click to reply to this thread Reply

Re: IO: Custom Reading and Writing with Serializable

readObject should always start by calling either defaultReadObject or readFields. Likewise, writeObject should call defaultWriteObject or putFields.

Unless your implementation differs greatly from the logical data of the object (for instance a collection), I suggest using fields rather than creating your own binary format. Even if for some reason you end up wildly changing your logical data, you can still use readFields/putFields.

thread.rss_message