5

考虑以下三个类:

  • EntityTransformer包含将实体与字符串相关联的映射
  • Entity是一个包含 ID 的对象(由 equals / hashcode 使用),并且包含对EntityTransformer的引用(注意循环依赖)
  • SomeWrapper包含一个EntityTransformer,并维护一个 Map 关联Entity的标识符和相应的Entity对象。

以下代码将创建一个 EntityTransformer 和一个 Wrapper,向 Wrapper 添加两个实体,对其进行序列化、反序列化并测试这两个实体是否存在:

public static void main(String[] args)
    throws Exception {

    EntityTransformer et = new EntityTransformer();
    Wrapper wr = new Wrapper(et);

    Entity a1 = wr.addEntity("a1");  // a1 and a2 are created internally by the Wrapper
    Entity a2 = wr.addEntity("a2");

    byte[] bs = object2Bytes(wr);
    wr = (SomeWrapper) bytes2Object(bs);

    System.out.println(wr.et.map);
    System.out.println(wr.et.map.containsKey(a1));
    System.out.println(wr.et.map.containsKey(a2));
}

输出是:

{a1=whatever-a1, a2=whatever-a2}

错误的

真的

所以基本上,序列化以某种方式失败了,因为地图应该包含两个实体作为键。我怀疑 Entity 和 EntityTransformer 之间的循环依赖关系,实际上,如果我将 Entity 的 EntityManager 实例变量设为静态,它就可以工作。

问题1:鉴于我被这种循环依赖所困扰,我该如何克服这个问题?

另一件非常奇怪的事情:如果我删除维护包装器中标识符和实体之间关联的地图,一切正常......??

问题2:有人明白这里发生了什么吗?

如果您想测试它,贝娄是一个完整的功能代码:

在此先感谢您的帮助 :)

public class SerializeTest {

public static class Entity
        implements Serializable
 {
    private EntityTransformer em;
    private String id;

    Entity(String id, EntityTransformer em) {
        this.id = id;
        this.em = em;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Entity other = (Entity) obj;
        if ((this.id == null) ? (other.id != null) : !this.id.equals(
            other.id)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 97 * hash + (this.id != null ? this.id.hashCode() : 0);
        return hash;
    }

    public String toString() {
        return id;
    }
}

public static class EntityTransformer
    implements Serializable
{
    Map<Entity, String> map = new HashMap<Entity, String>();
}

public static class Wrapper
    implements Serializable
{
    EntityTransformer et;
    Map<String, Entity> eMap;

    public Wrapper(EntityTransformer b) {
        this.et = b;
        this.eMap = new HashMap<String, Entity>();
    }

    public Entity addEntity(String id) {
        Entity e = new Entity(id, et);
        et.map.put(e, "whatever-" + id);
        eMap.put(id, e);

        return e;
    }
}

public static void main(String[] args)
    throws Exception {
    EntityTransformer et = new EntityTransformer();
    Wrapper wr = new Wrapper(et);

    Entity a1 = wr.addEntity("a1");  // a1 and a2 are created internally by the Wrapper
    Entity a2 = wr.addEntity("a2");

    byte[] bs = object2Bytes(wr);
    wr = (Wrapper) bytes2Object(bs);

    System.out.println(wr.et.map);
    System.out.println(wr.et.map.containsKey(a1));
    System.out.println(wr.et.map.containsKey(a2));
}



public static Object bytes2Object(byte[] bytes)
    throws IOException, ClassNotFoundException {
    ObjectInputStream oi = null;
    Object o = null;
    try {
        oi = new ObjectInputStream(new ByteArrayInputStream(bytes));
        o = oi.readObject();
    }
    catch (IOException io) {
        throw io;
    }
    catch (ClassNotFoundException cne) {
        throw cne;
    }
    finally {
        if (oi != null) {
            oi.close();
        }
    }

    return o;
}

public static byte[] object2Bytes(Object o)
    throws IOException {
    ByteArrayOutputStream baos = null;
    ObjectOutputStream oo = null;
    byte[] bytes = null;
    try {
        baos = new ByteArrayOutputStream();
        oo = new ObjectOutputStream(baos);

        oo.writeObject(o);
        bytes = baos.toByteArray();
    }
    catch (IOException ex) {
        throw ex;
    }
    finally {
        if (oo != null) {
            oo.close();
        }
    }

    return bytes;
}
}

编辑

有一个很好的总结是什么可能是这个问题: http ://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4957674

问题是 HashMap 的 readObject() 实现,为了重新散列地图,调用它的一些键的 hashCode() 方法,不管这些键是否已经完全反序列化。

如果键包含(直接或间接)对映射的循环引用,则在反序列化期间可以执行以下顺序 --- 如果键在哈希映射之前写入对象流:

  1. 实例化密钥
  2. 反序列化密钥的属性 2a。反序列化 HashMap(直接或间接被 key 指向)2a-1。实例化 HashMap 2a-2。读取键和值 2a-3。在键上调用 hashCode() 以重新散列地图 2b。反序列化密钥的剩余属性

由于 2a-3 在 2b 之前执行,因此 hashCode() 可能会返回错误的答案,因为键的属性尚未完全反序列化。

现在这并不能完全解释为什么如果从 Wrapper 中删除 HashMap 或移至 EntityTransformer 类可以解决问题。

4

3 回答 3

4

这是循环初始化的问题。虽然 Java 序列化可以处理任意周期,但初始化必须以某种顺序发生。

在 AWT 中有一个类似的问题,其中Component( Entity) 包含对其父级Container( EntityTransformer) 的引用。AWT 所做的是在Component transient.

transient Container parent;

所以现在每个人都Component可以在重新添加之前完成它的初始化Container.readObject

    for(Component comp : component) {
        comp.parent = this;
于 2012-04-05T18:05:17.297 回答
3

更奇怪的是,如果你这样做

Map<Entity, String> map = new HashMap<>(wr.et.map);
System.out.println(map.containsKey(a1));
System.out.println(map.containsKey(a2));

序列化和反序列化后,您将获得正确的输出。

还:

for( Entity a : wr.et.map.keySet() ){
    System.out.println(a.toString());
    System.out.println(wr.et.map.containsKey(a));
}

给出:

a1
false
a2
true

我想你发现了一个错误。最有可能的是,序列化以某种方式破坏了散列。事实上,我想你可能已经发现了这个错误

于 2012-04-05T16:27:18.890 回答
0

是否可以重写序列化,在序列化之前将引用转换为键值,然后在反序列化时将其转换回?

在序列化时找到 EntityTransformer 的哈希键并改用该值(可能在名为 parentKey 的结构中提供一个值)并将引用清空,这似乎非常简单。然后在重新序列化时,您会找到与该键值关联的 EntityTransformer 并分配其引用。

于 2012-04-05T15:55:29.007 回答