有时候(实际上很多)我们在Java中得到一个情况,其中两个对象指向同一个东西。现在,如果我们将这些单独序列化,则序列化表单具有对象的单独副本是非常合适的,因为它应该可以打开而不使用另一个。但是,如果我们现在对它们进行反序列化,我们发现它们仍然是分开的。有没有办法将它们重新组合在一起?
示例如下。
public class Example {
private static class ContainerClass implements java.io.Serializable {
private ReferencedClass obj;
public ReferencedClass get() {
return obj;
}
public void set(ReferencedClass obj) {
this.obj = obj;
}
}
private static class ReferencedClass implements java.io.Serializable {
private int i = 0;
public int get() {
return i;
}
public void set(int i) {
this.i = i;
}
}
public static void main(String[] args) throws Exception {
//Initialise the classes
ContainerClass test1 = new ContainerClass();
ContainerClass test2 = new ContainerClass();
ReferencedClass ref = new ReferencedClass();
//Make both container class point to the same reference
test1.set(ref);
test2.set(ref);
//This does what we expect: setting the integer in one (way of accessing the) referenced class sets it in the other one
test1.get().set(1234);
System.out.println(Integer.toString(test2.get().get()));
//Now serialise the container classes
java.io.ObjectOutputStream os = new java.io.ObjectOutputStream(new java.io.FileOutputStream("C:\\Users\\Public\\test1.ser"));
os.writeObject(test1);
os.close();
os = new java.io.ObjectOutputStream(new java.io.FileOutputStream("C:\\Users\\Public\\test2.ser"));
os.writeObject(test2);
os.close();
//And deserialise them
java.io.ObjectInputStream is = new java.io.ObjectInputStream(new java.io.FileInputStream("C:\\Users\\Public\\test1.ser"));
ContainerClass test3 = (ContainerClass)is.readObject();
is.close();
is = new java.io.ObjectInputStream(new java.io.FileInputStream("C:\\Users\\Public\\test2.ser"));
ContainerClass test4 = (ContainerClass)is.readObject();
is.close();
//We expect the same thing as before, and would expect a result of 4321, but this doesn't happen as the referenced objects are now separate instances
test3.get().set(4321);
System.out.println(Integer.toString(test4.get().get()));
}
}
答案 0 :(得分:3)
readResolve()
method允许这样做(当然,首先你必须定义你将如何决定哪些对象是“相同的”)。但是将两个对象序列化到同一个文件中要容易得多 - ObjectOut / InputStream会保存它已序列化/反序列化的所有对象的记录,并且只存储和返回它已经看到的对象的引用。
答案 1 :(得分:1)
我为我正在构建的应用程序服务器/对象数据库做了类似的事情。您有什么要求 - 为什么需要这样做?如果您的要求比应用程序服务器少,那么也许其他一些设计可以更轻松地解决它。
如果您仍想继续,请按照以下步骤操作:
首先,您需要通过覆盖ObjectOutputStream.replaceObject()和ObjectInputStream.resolveObject()方法来挂钩序列化过程。有关示例,请参阅我的ObjectSerializer。
序列化对象时,必须为每个要具有唯一标识的对象实例分配唯一的ID - 这种对象通常称为实体。当对象引用其他实体时,您必须使用包含引用实体ID的占位符对象替换其他实体。
然后,当反序列化对象时,必须将每个占位符对象替换为具有该ID的实际实体对象。您需要跟踪已加载到内存中的实体对象实例及其ID,以便每个ID只创建一次实例。如果实体尚未加载到内存中,则必须从保存它的位置加载它。有关示例,请参阅我的EntityManager。
如果要进行延迟加载,为避免在不需要时将整个对象图加载到内存中,必须执行与transparent references类似的操作。查看他们的实施here。如果你做得那么远,你也可以从我的项目中复制这些部分(包entities,entities.tref,serial,也许还有context) - 它有许可许可 - 并根据您的需要进行修改(即删除您不需要的东西)。
答案 2 :(得分:1)
与上述答案一样,readResolve
是关键,因为它允许您将“复制”对象替换为您想要的对象。
假设您的类实现了hashCode()和equals(),您可以通过创建一个静态WeakHashMap来实现重复数据删除,该静态WeakHashMap包含对仍在内存中的已创建对象的所有引用。 e.g。
class ReferencedClass implements Serializable
{
static private Map<ReferencedClass, Reference<ReferencedClass>> map = new WeakHashMap<ReferencedClass, Reference<ReferencedClass>>;
static public ReferencedClass findOriginal(ReferencedClass obj)
{
WeakReference<ReferencedClass> originalRef = map.get(obj);
ReferencedClass original = originalRef==null ? null : originalRef.get();
if (original==null)
{
original = obj;
map.put(original, new WeakReference<ReferencedClass>(original));
}
return original;
}
static public ReferencedClass()
{
findOriginal(this);
}
private Object readResolve()
{
return findOriginal(this);
}
}
反序列化时,readResolve()
调用RerencedClass.findOriginal(this)
来获取当前原始实例。如果此类的实例仅通过反序列化创建,那么这将按原样工作。如果你也在构造对象(使用new运算符),那么你的构造函数也应该调用findOriginal,传递它,这样那些对象也会被添加到池中。
有了这些更改后,两个ContainerClass
实例都会指向同一个ReferenceClass
实例,即使它们是独立反序列化的。