Java序列化顺序和自引用

时间:2013-11-20 13:03:35

标签: java serialization self-reference

这是一个简化的课程:

class Foo implements Serializable {
    private static final long serialVersionUID = 1L;

    private Integer id;
    private Set<Foo> children;

    public Foo( Integer id ) {
        if( id == null ) {
            throw new IllegalArgumentException( );
        }
        this.id = id;
        this.children = new HashSet<Foo>( 16 );
    }
    @Override public int hashCode( ) {
        return id.hashCode( );
    }

    ...
}

如您所见,它包含一组自身,并使用其id属性生成哈希。但是当对象具有自引用循环时,我遇到了一个问题:

当对象被反序列化时,处理首先跟随子对象到最深的对象,然后向后构建。这通常很好,但如果一个对象在其children集中包含一个较高的对象,它就会中断:当它试图将此对象添加到其HashSet时,它会调用hashCode,但是id尚未为该对象加载,因此它与NullPointerException崩溃。

(我花了很长时间来跟踪它!)

所以我的问题是:我可以控制序列化的顺序吗?我需要在{/ em> id之前children序列化(和反序列化)

2 个答案:

答案 0 :(得分:1)

您的分析似乎是正确的。您应该实现自定义序列化逻辑。假设id是唯一的,请考虑以下内容:

  • 序列化时,为每个节点存储其ID以及子ID列表(您可能希望添加此处省略的其他字段)
  • 在反序列化时,维护已访问ID到其记录的辅助映射。按顺序扫描序列化列表,对于遇到的每个新ID(父项或子项),创建一条新记录(初始化id字段)并将其存储在地图中。现在可以将子项安全地添加到父级的哈希集中,因为它们已经初始化了其id字段。

答案 1 :(得分:0)

我遵循了jdev的建议,并且(经过一些研究)使用Externalizable接口实现了我自己的序列化,如下所示:

class Foo implements Externalizable {
    /**
     * DO NOT USE THIS CONSTRUCTOR! This only exists for Externalizable
     */
    public Foo( ) {
        id = null;
        children = null;
    }

    @Override public void writeExternal( final ObjectOutput o ) throws IOException {
        o.writeInt( id.intValue( ) );
        o.writeObject( children );
    }

    @SuppressWarnings( "unchecked" )
    @Override public void readExternal( final ObjectInput o ) throws IOException, ClassNotFoundException {
        id = Integer.valueOf( o.readInt( ) );
        children = (Set<Foo>) o.readObject( );
    }

    // rest of code as before
}

我唯一的抱怨是它现在需要一个公共的无参数构造函数,但我可以忍受(至少这是一个内部API)。

现在所有事情都按照正确的顺序完成,我没有问题反序列化。