用作锁的瞬态最终字段为空
Posted
技术标签:
【中文标题】用作锁的瞬态最终字段为空【英文标题】:A transient final field used as a lock is null 【发布时间】:2012-09-01 17:06:55 【问题描述】:以下代码抛出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);
这是输出:
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)
lock
怎么可能为空?
【问题讨论】:
@nicholas.hauschild 不仅允许而且鼓励自答问题。 【参考方案1】:如前所述,下面的声明并不像预期的那样起作用:
transient final Object foo = new Object()
transient
关键字将阻止成员被序列化。 反序列化过程中不支持使用默认值初始化,因此反序列化后foo
将变为null
。
final
关键字将阻止您在设置成员后对其进行修改。这意味着您在反序列化实例上永远被 null
卡住。
在任何情况下,您都需要删除 final
关键字。这会牺牲不变性,但对于private
成员来说通常不会成为问题。
那么你有两个选择:
选项 1:覆盖 readObject()
transient Object foo = new Object();
@Override
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
in.defaultReadObject();
foo = new Object();
创建新实例时,foo
将被初始化为其默认值。反序列化时,您的自定义 readObject()
方法将处理该问题。
这将适用于 JRE,但不适用于 android,因为 Android 的 Serializable
实现缺少 readObject()
方法。
选项 2:延迟初始化
声明:
transient Object foo;
访问时:
if (foo == null)
foo = new Object();
doStuff(foo);
您必须在您访问 foo
的代码中的任何位置执行此操作,这可能比第一个选项工作更多且更容易出错,但它适用于 JRE 和 Android。
【讨论】:
【参考方案2】:声明为transient
的任何字段都不会被序列化。此外,根据this blog post,字段值甚至没有初始化为默认构造函数设置的值。当transient
字段为final
时,这会产生挑战。
根据the Serializable javadoc,可以通过实现以下方法来控制反序列化:
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
我根据this excellent *** answer提出了以下解决方案:
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);
运行它会产生以下输出(没有NullPointerException
):
About to synchronize
lock used
About to synchronize
lock used
【讨论】:
您引用的博客文章根本没有说明默认值,更不用说您在上面声明的内容了。这甚至没有意义:它们会被初始化成什么else?您的代码解决方案也过于复杂:您不需要对此进行反思。 博客文章说,“声明为 final 的实例成员字段也可能是临时的,但如果是这样,您将面临一个难以解决的问题......当您反序列化您的对象时将不得不手动初始化该字段,[但编译器抱怨因为它是最终的]...现在,当您反序列化该类时,您的记录器将是一个空对象,因为它是瞬态的。”这是我的答案解决的问题。如果没有反思,you 如何建议我们设置final
字段的值?你的评论也太刻薄了:你不需要为此开玩笑。
博客没有说出你所说的。时期。我的评论也没有说出您的主张:其余部分可以简化为“过度”一词,这几乎不是“刻薄的”或“刻薄的”。这让我很困惑。
抱歉,听起来我们对“默认”这个重载的词有误解。在我上面的帖子中,我指的是在类定义中设置的值,即在构造之前设置的值。
请注意,Serializable
的 Android 实现缺少 readObject()
方法,因此该解决方案仅适用于 JRE(和完全兼容的实现),但无法在 Android 上编译。【参考方案3】:
A transient final field used as a lock is null
以下是关于瞬态变量的一些事实:
- 在实例变量上使用瞬态关键字时,将阻止该实例变量被序列化。
- 在反序列化时,瞬态变量会得到它们的默认值.....
例如:
对象引用变量到null
int 到0
布尔到false,
等......
这就是你在反序列化时获得NullPointerException
的原因...
【讨论】:
以上是关于用作锁的瞬态最终字段为空的主要内容,如果未能解决你的问题,请参考以下文章
判断字符串为空为 null 为 whitespace 工具类