【发布时间】:2012-04-05 15:50:15
【问题描述】:
考虑以下三个类:
- EntityTransformer 包含将 Entity 与字符串相关联的映射
- Entity 是一个包含 ID 的对象(由 equals / hashcode 使用),其中包含对 EntityTransformer 的引用(注意循环依赖)
- SomeWrapper 包含一个 EntityTransformer,并维护一个关联 Entity 的标识符和对应的 Entity 对象的 Map。李>
以下代码将创建一个 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:鉴于我被这种循环依赖所困扰,我该如何克服这个问题?
另一件非常奇怪的事情:如果我删除维护 Wrapper 中标识符和实体之间的关联的 Map,一切正常...??
问题 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() 方法, 无论这些密钥是否已完全反序列化。
如果键包含(直接或间接)对 map,以下执行顺序是可能的 反序列化 --- 如果之前将密钥写入对象流 哈希图:
- 实例化密钥
- 反序列化键的属性 2a.反序列化HashMap(直接或间接被key指向) 2a-1。实例化 HashMap 2a-2。读取键和值 2a-3。在键上调用 hashCode() 以重新散列地图 2b。反序列化密钥的剩余属性
由于2a-3在2b之前执行,hashCode()可能会返回错误 回答,因为key的属性还没有完全 反序列化。
现在这并不能完全解释为什么如果从 Wrapper 中删除 HashMap 或移至 EntityTransformer 类可以解决问题。
【问题讨论】:
-
为什么你的
equals逻辑如此复杂?为什么不只是return (this.id == null) ? (other.id == null ) : this.id.equals( other.id );? -
此示例的等号/哈希码是由 NetBeans 自动生成的,因为这不是我问题的真正根源,所以我没有花时间查看它
-
@dty 我的意思是用它替换 if 语句和最后一个 return 语句。我不是白痴。
-
我同意,但如果我每次有人写一个看起来和你写的一模一样的 equals() 方法时都有一美元,我会是一个非常富有的人。我什至不是在开玩笑。有很多人认为你写的东西作为整个 equals() 方法是完全可以接受的。另外,作为一名软件工程师,我更喜欢精确的语言,而不是希望人们以特定的方式解释我的通信。
-
大概是this reported bug的体现。
标签: java serialization deserialization