Inner classes were added to Java in version 1.1. Inner classes always occur inside the curly braces of an outer class, and can occur on unlimited nesting levels. Two different kinds exist:
This Blog is about the difference between these two, and the sometimes fatal consequences of serializing instances of a non-static inner class. I do not cover anonymous and local classes here.
Static inner classes behave like most people would expect from an inner class. They do not require the existence of an outer "parent" object. They are like classes of a sub-package, but referenced through the class, and behave like normal classes, except that you can set access modifiers on them (private, protected, public
, default), which is quite useful.
Such an object is constructed like the following:
public class StaticInnerClassExample
{
private static class Cat
{
}
public static void main(String[] args) {
final Cat cat = new StaticInnerClassExample.Cat();
}
}
So the construction happens by new OuterClass.InnerClass()
.
Non-static inner classes on the other hand require the existence of an outer "parent" object. Furthermore they also hold an invisible reference to their outer object, generated by the compiler. You can see this hidden reference in the debugger as "this$0
".
The construction of a non-static inner object looks a little unusual, you must call new
on the parent instance, new OuterClass().new InnerClass()
:
public class NonStaticInnerClassExample
{
private class Cat
{
}
public static void main(String[] args) {
final Cat cat = new NonStaticInnerClassExample().new Cat();
}
}
This makes the difference clear: the non-static always needs an outer object to come to life.
What can we do with serialization? For example, in case all your classes implement Serializable
, you could write the whole object graph of your application to a file before terminating it. On next startup you could load the application from that file. It then would be in exactly the same state as it was when you terminated it!
The following is about Java default serialization. Serialization of an object can be overridden by implementing readObject()
and writeObject()
, which I will not cover here.
Serialization reads and writes the values of the instance-fields of an object, called "the state of the object". Methods do not get serialized, they belong to the class.
If there is a reference to another object, also this object gets serialized, recursively. In case there is something in the resulting object-graph that doesn't implement the interface Serializable, a NotSerializableException
will be thrown.
Serialization ignores static
fields, and fields tagged as transient
(a Java language keyword).
Mind that serializing instances of inner classes is generally discouraged due to compiler-specific mechanisms around it.
Now that we know about inner classes and serialization we can try out what happens when you serialize an object of a non-static inner class.
1 | public class CatPack implements Serializable |
The CatPack
class encapsulates its Cat
members using a non-static inner class. The Cat.toString()
implementation shows that non-static inner classes have access to private fields and methods of the outer class, like packName
and mateNames()
. The implicit pointer to the outer object provides that (static inner classes don't have such).
So far so good, but what happens when serializing a Cat
instance? Will it still be able to enumerate the names of pack cats after? Here is the according test code:
1 | public class InnerClassSerializationGotcha |
First a CatPack
gets constructed. Two members get added, "Garfield" and "Catbert". The add()
method returns the created Cat
instance, thus we can serialize "Catbert".
We serialize through an ObjectOutputStream
based on an in-memory ByteArrayInputStream
, which is then the source for the ObjectInputStream
that de-serializes "Catbert". Now the question rises: what exactly has been sent over the line? Just "Catbert", or the whole pack?
Answer is: the whole CatPack
went through the line! The output proves this:
Catbert from Garfield Clan, having: Garfield, Catbert
→ How could the Cat
know the names of its mates after serialization when they did not travel with it?
Even when you know about this implicit pointer to the outer object, you will forget about it. Thus I recommend to always use static inner classes when there is no good reason for not doing it.
Which doesn't mean that I recommend the static
keyword generally, inner classes is just a special case. In any other case try to avoid static
, it has lots of disadvantages.
ɔ⃝ Fritz Ritzberger, 2018-07-15