The following code throws a NullPointerException
.
import java.io.*;
public class NullFinalTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Foo foo = new Foo();
foo.useLock();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
new ObjectOutputStream(buffer).writeObject(foo);
foo = (Foo) new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())).readObject();
foo.useLock();
}
public static class Foo implements Serializable {
private final String lockUsed = "lock used";
private transient final Object lock = new Object();
public void useLock() {
System.out.println("About to synchronize");
synchronized (lock) { // <- NullPointerException here on 2nd call
System.out.println(lockUsed);
}
}
}
}
Here is the output:
About to synchronize
lock used
About to synchronize
Exception in thread "main" java.lang.NullPointerException
at NullFinalTest$Foo.useLock(NullFinalTest.java:18)
at NullFinalTest.main(NullFinalTest.java:10)
How can lock
possibly be null?
A transient final field used as a lock is null
Here are few facts about the transient variable:
- Transient keyword when used on an instance variable, will prevent that instance variable to be serialized.
- On De-serialization, the transient variable get to their Default values.....
Eg:
null
0
false,
etc.......So thats the reason you are getting a
NullPointerException
, when deserializing it...As pointed out before, the declaration below does not work as one might expect:
The
transient
keyword will prevent the member from being serialized. Initialization with a default value is not honored during deserialization, thereforefoo
will benull
after deserialization.The
final
keyword will prevent you from modifiying the member once it has been set. This means you're stuck withnull
forever on a deserialized instance.In any case you will need to drop the
final
keyword. This will sacrifice immutability, but should not usually be an issue forprivate
members.Then you have two options:
Option 1: Override
readObject()
When creating a new instance,
foo
will be initialized to its default value. When deserializing, your customreadObject()
method will take care of that.This will work on JRE but not on Android, as Android's implementation of
Serializable
lacks thereadObject()
method.Option 2: Lazy initialization
Declaration:
On access:
You would have to do this wherever in your code you access
foo
, which may be more work and more error-prone than the first option, but it will work on JRE and Android alike.Any field that is declared
transient
is not serialized. Moreover, according to this blog post, field values are not even initialized to the values that would be set by a default constructor. This creates a challenge when atransient
field isfinal
.According to the Serializable javadoc, deserialization can be controlled by implementing the following method:
I came up with the following solution, based on this excellent StackOverflow answer:
Running it results in the following output (no
NullPointerException
):