Java 用作锁的临时最终字段为空
下面的代码抛出一个Java 用作锁的临时最终字段为空,java,serialization,nullpointerexception,final,transient,Java,Serialization,Nullpointerexception,Final,Transient,下面的代码抛出一个NullPointerException import java.io.*; public class NullFinalTest { public static void main(String[] args) throws IOException, ClassNotFoundException { Foo foo = new Foo(); foo.useLock(); ByteArrayOutputStream bu
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);
}
}
}
}
lock
怎么可能为空?任何声明为transient
的字段都不会序列化。此外,根据,字段值甚至没有初始化为默认构造函数设置的值。当瞬态
字段为最终
时,这会产生一个质询
根据,可以通过实现以下方法来控制反序列化:
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
我提出了以下解决方案,基于:
运行它会产生以下输出(无NullPointerException
):
用作锁的临时最终字段为空
以下是关于瞬态变量的一些事实:
-在实例变量上使用Transient关键字时,将阻止序列化该实例变量。
-反序列化时,瞬态变量将达到其默认值
例如:
- 对象引用变量到
null
- int到
0
- 布尔值设置为
etcfalse、
因此,这就是您得到一个
NullPointerException
的原因,当反序列化它时…正如前面所指出的,下面的声明并不像人们预期的那样起作用:
transient final Object foo = new Object()
transient
关键字将阻止序列化成员在反序列化过程中,不会使用默认值初始化,因此反序列化后foo
将null
设置成员后,final
关键字将阻止您修改该成员。这意味着您在反序列化实例上永远只能使用null
在任何情况下,您都需要删除final
关键字。这将牺牲不变性,但通常不应成为private
成员的问题
那么您有两个选择:
选项1:覆盖readObject()
创建新实例时,foo
将初始化为其默认值。反序列化时,您的自定义readObject()
方法将处理该问题
这将适用于JRE,但不适用于Android,因为Android的Serializable
实现缺少readObject()
方法
选项2:延迟初始化
声明:
transient Object foo;
关于访问:
if (foo == null)
foo = new Object();
doStuff(foo);
您必须在代码中访问
foo
的任何地方执行此操作,这可能比第一个选项更容易工作,也更容易出错,但它同样适用于JRE和Android。@nicholas.hauschild不仅允许自答问题,但也值得鼓励。你引用的博客文章根本没有提到默认值,更不用说你上面所说的了。这甚至没有意义:它们还会被初始化成什么?您的代码解决方案也过于复杂:您不需要对此进行反射。博客文章说,“声明为final的实例成员字段也可能是暂时的,但如果是这样,您将面临一个有点难以解决的问题……当您反序列化对象时,您必须手动初始化字段,[但编译器会抱怨,因为它是最终的]……现在,当您反序列化该类时,您的记录器将是一个空对象,因为它是瞬态的。”这就是我的答案所解决的问题。在没有思考的情况下,你建议我们如何设置final
字段的值?你的评论也过于尖刻:你不需要为此大惊小怪。博客没有说你说的话。句号。我的评论也没有说你声称的话:剩下的部分可以简化为单词“过度”,这几乎不是“尖刻的”或“尖刻的”。这让我很困惑。对不起,听起来我们对这个超载的词“默认值”有误解在我上面的帖子中,我指的是类定义中设置的值,即构建之前设置的值。请注意,Serializable
的Android实现缺少readObject()
方法,因此此解决方案将只在JRE(和完全兼容的实现)上工作,而不会在Android上编译。
transient final Object foo = new Object()
transient Object foo = new Object();
@Override
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
foo = new Object();
}
transient Object foo;
if (foo == null)
foo = new Object();
doStuff(foo);