I've just had to deal with an error caused by improper java serialization and deserialization. I thought I'd document it so that I don't forget.
An instance of a non-static inner class in Java has a hidden reference to the instance of the outer class that contains it. This reference is used when accessing members of the outer class. By getting the serialization wrong I was able to create an instance of an inner class that didn't know what it's outer class instance was. This leads to a NullPointerException when accessing members of the outer class. If the method called is private the message at least gives a hint of the problem, if it's not then the whole thing is pretty confusing.
To start with here's a very basic Java program that creates an instance of a class Outer1 that includes a reference to an instance of an inner class Inner1. This is then serialized and deserialized. If you compile and execute the program you get.
bash-2.05b$ java -cp . Outer1 Before printString(outerField) printString(innerField) After printString(outerField) printString(innerField)
Say I want to implement my own serialization and deserialization for the inner class. All I need to do is add writeObject and readObject methods like this.
private void writeObject(ObjectOutputStream oos)
throws IOException {
oos.writeObject(innerField);
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
innerField = (String)ois.readObject();
}
Notice that I don't call the defaultWriteObject and defaultReadObject methods on the ObjectOutputStream and ObjectInputStream. After all, my class is a direct subclass of Object and I'm handling all of its fields myself. Here's the modified program but when I run it I get a NullPointerException. Here are the messages
bash-2.05b$ java -cp . Outer2 Before printString(outerField) printString(innerField) After printString(outerField) java.lang.NullPointerException at Outer2.access$100(Outer2.java:9) at Outer2$Inner2.output(Outer2.java:30) at Outer2.output(Outer2.java:17) at Outer2.main(Outer2.java:66)
If printString(String s) is made public the messages are even more cryptic.
bash-2.05b$ java -cp . Outer2 Before printString(outerField) printString(innerField) After printString(outerField) java.lang.NullPointerException at Outer2$Inner2.output(Outer2.java:30) at Outer2.output(Outer2.java:17) at Outer2.main(Outer2.java:66)
Note that Outer2.java:30 is printString(innerField); and Outer2.java:9 is public class Outer2. The problem is that while on line Outer2.java:17 the instance of Outer2 is able to call the output() method on the Inner2 instance (inner.output();) that inner code instance is unable to call the printString(String s) on the Outer2 instance.
And why is this? It's because I didn't call defaultWriteObject() and defaultReadObject(). As well as the user defined fields in a class these methods also take care of, for an inner class, the reference to the enclosing outer class instance. Modifying the writeObject and readObject methods as below cures the problem.
private void writeObject(ObjectOutputStream oos)
throws IOException {
oos.defaultWriteObject();
oos.writeObject(innerField);
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
innerField = (String)ois.readObject();
}
This version of the program runs correctly and produces:
bash-2.05b$ java -cp . Outer3 Before printString(outerField) printString(innerField) After printString(outerField) printString(innerField)Posted by Alex at October 21, 2002 09:22 PM