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. (sponsored)
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.
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.
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.
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:
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;
publicclass Person implements Serializable {
privateint 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) { thrownew IllegalArgumentException("First name can't be null."); }
if(lname == null) { thrownew IllegalArgumentException("First name can't be null."); }
this.firstName = fname;
this.lastName = lname;
this.age = age;
}
publicint getAge() {
return age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Collection<Person> getChildren() {
return children;
}
public Person getParent() {
return parent;
}
publicvoid setChildren(Collection<Person> children) {
this.children = children;
}
publicvoid setChildren(Person... children) {
setChildren(
children == null ?
new ArrayList<Person>() :
new ArrayList<Person>(Arrays.asList(children))
);
}
publicvoid setParent(Person parent) {
this.parent = parent;
}
@Override
public String toString() {
return firstName + " " + lastName + " - " + age + "yrs.";
}
privatevoid readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
age = in.readInt();
firstName = in.readUTF();
lastName = in.readUTF();
children = (Collection<Person>)in.readObject();
parent = (Person)in.readObject();
}
privatevoid writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(age);
out.writeUTF(firstName);
out.writeUTF(lastName);
out.writeObject(children);
out.writeObject(parent);
}
publicstaticvoid 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:
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:
privatestaticfinallong serialVersionUID = 1L;
privatestaticfinalint CLASS_VERSION = 3;
[...]
privatevoid writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(CLASS_VERSION);
[...]
}
privatevoid 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).
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.
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.
IO: Custom Reading and Writing with Serializable
At 2:04 AM on Nov 27, 2006, R.J. Lorimer wrote:
Fresh Jobs for Developers Post a job opportunity
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(...)andwriteObject(...).The serialization documentation gives a quick once over of the implementation of these methods - here is a snippet:
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:
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
Serializablethat fits a similar purpose.java.io.Externalizableextendsjava.io.Serializable, and specifies two very similar methods to writeObject and readObject -writeExternal(ObjectOutput)andreadExternal(ObjectInput). There are some key differences between Externalizable and Serializable, however: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).
2 replies so far (
Post your own)
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,
MiG Java Calendar Component, MiG Layout for Swing/SWT (Vote -> JDK)
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.