Java序列化问题:public static final对象反序列化后非原引用
解决Serializable树形结构中静态根对象反序列化重复创建的问题
嘿,这个问题我之前在做树形数据持久化的时候也碰到过!本质原因是Java序列化的一个小坑:虽然static字段本身不会被序列化,但如果你的树形节点对象引用了这个public static final的ROOT实例,那么序列化的时候,ROOT作为被引用的对象会被写入序列化流里。反序列化时,JVM会重新创建一个Tree实例,而不是直接复用已经存在的static常量,这就导致了引用不一致的问题。
最直接的解决方案:实现readResolve()钩子方法
Java序列化提供了readResolve()这个特殊的钩子方法——当对象反序列化完成后,JVM会自动调用这个方法,并且用方法返回的对象代替刚反序列化出来的实例。我们刚好可以利用这个特性,让反序列化后的ROOT对象直接指向原来的static常量。
完整代码示例
先看Tree类的实现,重点是readResolve()方法:
import java.io.Serializable; public class Tree implements Serializable { // 公共静态根常量 public static final Tree ROOT = new Tree("Root"); private String name; private Tree parent; public Tree(String name) { this.name = name; } // 常规的getter和setter,按需添加 public String getName() { return name; } public Tree getParent() { return parent; } public void setParent(Tree parent) { this.parent = parent; } // 关键:实现readResolve方法 private Object readResolve() { // 判断当前反序列化的对象是不是根节点,这里用name作为标识 // 如果你的根节点有更唯一的标识(比如专门的flag字段),用那个更稳妥 if ("Root".equals(this.name)) { // 返回原来的静态常量,替换反序列化出来的新对象 return ROOT; } // 非根节点直接返回自身即可 return this; } }
然后是测试序列化和反序列化的代码,用来验证效果:
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializationTest { public static void main(String[] args) throws Exception { // 创建一个子节点,关联到ROOT Tree childNode = new Tree("Child Node"); childNode.setParent(Tree.ROOT); // 序列化子节点到文件 File serFile = new File("tree_data.ser"); try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(serFile))) { oos.writeObject(childNode); } // 反序列化读取 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(serFile))) { Tree deserializedChild = (Tree) ois.readObject(); // 验证反序列化后的父节点是否和原ROOT是同一个引用 System.out.println("反序列化后子节点的父节点 == 原ROOT:" + (deserializedChild.getParent() == Tree.ROOT)); // 正常情况下会输出true } } }
额外说明
readResolve()的访问修饰符设为private就够了,JVM会通过反射调用它,不需要对外暴露。- 如果你的ROOT节点没有合适的名称标识,可以给Tree类加一个
private final boolean isRoot字段,在创建ROOT时设为true,其他节点设为false,这样判断起来更准确。 - 要是你还想优化序列化的空间,避免把ROOT的内容写入流,可以配合
writeReplace()方法,在序列化时把ROOT替换成一个轻量的标记对象,反序列化时再通过readResolve()替换回原ROOT,但这个场景下只用readResolve()就完全能解决问题了。
内容的提问来源于stack exchange,提问作者Stephen Ware
相关产品推荐
相关产品推荐

