Странное поведение сериализации с HashMap

Рассмотрим три следующих класса:

  • EntityTransformerсодержит карту, связывающую Entityсо строкой
  • Entity— это объект, содержащий идентификатор (используемый равными / хэш-кодом) и содержащий ссылку на EntityTransformer(обратите внимание на круговую зависимость)
  • SomeWrapperсодержит EntityTransformerи поддерживает карту, связывающую идентификаторы 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}

false

true

Таким образом, сериализация каким-то образом не удалась, так как карта должна содержать обе сущности в качестве ключей. Я подозреваю циклическую зависимость между Entity и EntityTransformer, и действительно, если я сделаю статической переменную экземпляра EntityManager Entity, это сработает.

Вопрос 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 map = new HashMap();
}

public static class Wrapper
    implements Serializable
{
    EntityTransformer et;
    Map eMap;

    public Wrapper(EntityTransformer b) {
        this.et = b;
        this.eMap = new HashMap();
    }

    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. Десериализовать атрибуты ключа 2а. Десериализовать HashMap (на который прямо или косвенно указывает ключ) 2а-1. Создайте экземпляр HashMap 2а-2. Чтение ключей и значений 2а-3. Вызовите hashCode() для ключей, чтобы повторно хэшировать карту 2б. Десериализовать оставшиеся атрибуты ключа

Поскольку 2a-3 выполняется до 2b, функция hashCode() может вернуть неверный результат. ответ, потому что атрибуты ключа еще не полностью десериализованный.

Теперь это не объясняет полностью, почему проблему можно решить, если удалить HashMap из Wrapper или перейти к классу EntityTransformer.

5
задан ecniv 6 April 2012 в 06:59
поделиться