Hãy xem xét ba loại sau đây:Weird hành vi serialization với HashMap
- EntityTransformer chứa một bản đồ liên kết một Entity với một String
- Entity là một đối tượng chứa một ID (được sử dụng bởi bình đẳng/hashcode) và chứa tham chiếu đến một số EntityTransformer (lưu ý phụ thuộc vòng tròn)
- SomeWrapper chứa một EntityTransformer, và duy trì một bản đồ liên kết định danh Entity 's và tương ứng Entity đối tượng.
Các mã sau sẽ tạo ra một EntityTransformer và một Wrapper, thêm hai thực thể đến Wrapper, serialize nó, deserialize nó và kiểm tra sự hiện diện của hai entitites:
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));
}
Đầu ra là:
{a1 = bất cứ điều gì-a1, a2 = bất cứ điều gì-a2}
sai
true
Vì vậy, về cơ bản, việc tuần tự hóa không thành công, vì bản đồ phải chứa cả hai thực thể dưới dạng Khóa. Tôi nghi ngờ sự phụ thuộc cyclic giữa Entity và EntityTransformer, và thực sự nếu tôi làm tĩnh biến EntityManager instance của Entity, nó hoạt động.
Câu hỏi 1: với điều kiện tôi bị kẹt với sự phụ thuộc theo chu kỳ này, làm thế nào tôi có thể khắc phục vấn đề này?
Một điều rất lạ: nếu tôi xóa Bản đồ duy trì liên kết giữa số nhận dạng và Thực thể trong Trình bao bọc, mọi thứ hoạt động tốt ... ??
Câu hỏi 2: ai đó hiểu điều gì đang xảy ra ở đây?
Bellow là một mã chức năng đầy đủ nếu bạn muốn thử nghiệm nó:
Cảm ơn trước sự giúp đỡ của bạn :)
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;
}
}
EDIT
Có một bản tóm tắt tốt về những gì có khả năng phát trong vấn đề này: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4957674
Vấn đề là việc thực hiện readObject() của HashMap, theo thứ tự để băm lại bản đồ, gọi phương thức hashCode() của một số khóa của nó, bất kể các khóa đó đã được deserialized hoàn toàn chưa.
Nếu một chìa khóa chứa (trực tiếp hoặc gián tiếp) một tham chiếu vòng tròn để bản đồ , thứ tự sau đây của thi thể trong deserialization --- nếu phím được ghi vào dòng đối tượng trước khi các hashmap:
- Khởi tạo khóa
- Deserialize các thuộc tính của khóa 2a. Deserialize HashMap (được gán trực tiếp hoặc gián tiếp bởi khóa) 2a-1. Khởi tạo HashMap 2a-2. Đọc khóa và giá trị 2a-3. Gọi hàm hashCode() trên các phím để băm lại bản đồ 2b. Deserialize thuộc tính còn lại của chính
Từ 2a-3 được thực hiện trước khi 2b, hashCode() có thể trả lại sai câu trả lời, bởi vì các thuộc tính của chính chưa được đầy đủ deserialized.
Bây giờ điều đó không giải thích đầy đủ lý do tại sao sự cố có thể được khắc phục nếu HashMap từ Wrapper bị xóa hoặc chuyển sang lớp EntityTransformer.
Tại sao cậu lại làm 'Logic equals' của bạn để phức tạp? Tại sao không chỉ 'return (this.id == null)? (other.id == null): this.id.equals (other.id); '? – trutheality
Bằng/mã băm được tự động tạo bởi NetBeans cho ví dụ này, và vì đây không phải là gốc của vấn đề của tôi, tôi không dành thời gian để xem xét nó – ecniv
@truheality vì vi phạm hợp đồng bằng! – dty