The temporary end field used as a lock is null

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 result:

 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 be null?

+6
java nullpointerexception serialization final transient
Sep 07 '12 at 19:47
source share
3 answers

A transient final field used as a lock is null

Here are some facts about the transition variable:

- The Transient keyword when used in an instance variable will prevent serialization of this instance variable.

- In De-serialization, the transition variable gets the default values .

For example:

  • Object reference null reference
  • int to 0
  • boolean to false, etc.

So the reason you get a NullPointerException when deserializing it ...

+14
Sep 07
source share

Any field declared by transient is not serialized. Moreover, according to this blog post , field values ​​are not even initialized to the values ​​that will be set by the default constructor. This creates a problem when the transient final field.

According to Serializable javadoc , deserialization can be controlled by implementing the following method:

 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException; 

I came up with the following solution based on https://stackoverflow.com> :

 import java.io.*; import java.lang.reflect.*; public class NullFinalTestFixed { 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) { System.out.println(lockUsed); } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); initLocks(this, "lock"); } } public static void initLocks(Object obj, String... lockFields) { for (String lockField: lockFields) { try { Field lock = obj.getClass().getDeclaredField(lockField); setFinalFieldValue(obj, lock, new Object()); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } } public static void setFinalFieldValue(Object obj, Field field, Object value) { Exception ex; try { field.setAccessible(true); Field modifiers = Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(obj, value); return; } catch (IllegalAccessException e) { ex = e; } catch (NoSuchFieldException e) { ex = e; } throw new RuntimeException(ex); } } 

Executing this result leads to the following output (no NullPointerException ):

 About to synchronize lock used About to synchronize lock used 
+4
Sep 07 '12 at 19:47
source share

As stated above, the declaration below does not work, as you might expect:

 transient final Object foo = new Object() 

The transient keyword will not allow member serialization. Initialization with the default value is not performed during deserialization , so foo will be null after deserialization.

The final keyword will prevent member modification after it is installed. This means that you are stuck with null forever in a deserialized instance.

In any case, you need to abandon the final keyword. This will sacrifice immutability, but usually this should not be a problem for private members.

Then you have two options:

Option 1: overriding readObject()

 transient Object foo = new Object(); @Override private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); foo = new Object(); } 

When creating a new instance, foo will be initialized by default. When deserializing, your custom readObject() method readObject() take care of that.

This will work on the JRE, but not on Android, as the readObject() method is missing in the Android Serializable implementation.

Option 2: Lazy Initialization

Declaration:

 transient Object foo; 

Available:

 if (foo == null) foo = new Object(); doStuff(foo); 

You will need to do this wherever you get access to foo in your code, which may be more difficult and more error prone than the first option, but it will work on both JRE and Android.

0
Jun 12 '16 at 15:54
source share



All Articles