Ошибка сериализации Java при возникновении циклической зависимости с набором

#java #hibernate #serialization

#java #гибернация #сериализация

Вопрос:

Мой проект — это java проект с EJB3 использованием гибернации и Weblogic сервера.

Для удобства (и, насколько я понимаю, это типично для hibernate ), некоторые из объектов содержат циклическую зависимость (родитель знает дочерний элемент, дочерний элемент знает родительский элемент). Кроме того, для некоторых дочерних классов метод hashCode() and equals() зависит от их родительского элемента (поскольку это уникальный ключ).

При работе я видел странное поведение — некоторые наборы, которые возвращались с сервера клиенту, хотя и содержали правильные элементы, вели себя так, как будто они не содержали ни одного. Например, простой тест, такой как этот: set.contains(set.toArray()[0]) возвращается false , хотя hashCode() метод является хорошим.

После обширной отладки я смог создать 2 простых класса, которые воспроизводят проблему (я могу заверить вас hashCode() , что функция в обоих классах является рефлексивной, транзитивной и симметричной):

 package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

public class ClientTest implements Serializable {
    public static void main(String[] args) throws Exception {
        SerializableClass serializationTest = new SerializableClass();
        FieldOfSerializableClass hashMember = new FieldOfSerializableClass();
        hashMember.setParentLink(serializationTest);
        serializationTest.setHashCodeField("Some string");
        serializationTest
                .setSomeSet(new HashSet<FieldOfSerializableClass>());
        serializationTest.getSomeSet().add(hashMember);
        System.out.println("Does it contain its member? (should return true!) "
                  serializationTest.getSomeSet().contains(hashMember));
        new ObjectOutputStream(new FileOutputStream("temp"))
                .writeObject(serializationTest);
        SerializableClass testAfterDeserialize = (SerializableClass) new ObjectInputStream(
                new FileInputStream(new File("temp"))).readObject();
        System.out.println("Does it contain its member? (should return true!) "
                  testAfterDeserialize.getSomeSet().contains(hashMember));

        for (Object o : testAfterDeserialize.getSomeSet()) {
            System.out.println("Does it contain its member by equality? (should return true!) "  o.equals(hashMember));
        }

    }

    public static class SerializableClass implements Serializable {
        private Set<FieldOfSerializableClass> mSomeSet;
        private String mHashCodeField;

        public void setSomeSet(Set<FieldOfSerializableClass> pSomeSet) {
            mSomeSet = pSomeSet;
        }

        public Set<FieldOfSerializableClass> getSomeSet() {
            return mSomeSet;
        }

        public void setHashCodeField(String pHashCodeField) {
            mHashCodeField = pHashCodeField;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;

            System.out.println("In hashCode - value of mHashCodeField: "
                      mHashCodeField);
            result = prime
                    * result
                      ((mHashCodeField == null) ? 0 : mHashCodeField.hashCode());
            return resu<
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            SerializableClass other = (SerializableClass) obj;

            if (mHashCodeField == null) {
                if (other.mHashCodeField != null) {
                    return false;
                }
            } else if (!mHashCodeField.equals(other.mHashCodeField))
                return false;
            return true;
        }

        private void readObject(java.io.ObjectInputStream in)
                throws IOException, ClassNotFoundException {
            System.out.println("Just started serializing");
            in.defaultReadObject();
            System.out.println("Just finished serializing");
        }
    }

    public static class FieldOfSerializableClass implements Serializable {
        private SerializableClass mParentLink;

        public void setParentLink(SerializableClass pParentLink) {
            mParentLink = pParentLink;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result
                      ((mParentLink == null) ? 0 : mParentLink.hashCode());

            return resu<
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            FieldOfSerializableClass other = (FieldOfSerializableClass) obj;
            if (mParentLink == null) {
                if (other.mParentLink != null) {
                    return false;
                }
            } else if (!mParentLink.equals(other.mParentLink))
                return false;
            return true;
        }
    }

}
 

Это привело к следующему результату:

 В хэш-коде - значение mHashCodeField: некоторая строка
 В хэш-коде - значение mHashCodeField: некоторая строка
 Содержит ли он свой элемент? (должно возвращать true!) true
 Только что начал сериализацию
 В hashCode - значение mHashCodeField: null
Только что завершила сериализацию
 В хэш-коде - значение mHashCodeField: некоторая строка
 Содержит ли он свой элемент? (должно возвращать true!) false
 Содержит ли он свой член по равенству? (должно возвращать true!) true

Это говорит мне о том, что порядок, в котором Java сериализует объект, неверен! Он начинает сериализацию набора перед строкой и, таким образом, вызывает вышеуказанную проблему.

Что мне делать в этой ситуации? Есть ли какой-либо вариант (помимо реализации readResolve для многих объектов …), чтобы направить java на сериализацию класса в определенном порядке? Кроме того, является ли принципиально неправильным, чтобы объект основывал его hashCode на своем родителе?

Редактировать: решение было предложено коллегой — поскольку я использую Hibernate, каждый объект имеет уникальный длинный идентификатор. Я знаю, что Hibernate указывает не использовать этот идентификатор в методе equals — но как насчет hashCode? Использование этого уникального идентификатора в качестве хэш-кода, по-видимому, решает вышеуказанную проблему с минимальным риском проблем с производительностью. Существуют ли какие-либо другие последствия для использования идентификатора в качестве хэш-кода?

ВТОРОЕ РЕДАКТИРОВАНИЕ: я пошел и реализовал свое частичное решение (все элементы теперь используют поле ID для функции hashCode() и больше не передают его другим элементам), но, увы, ошибки сериализации все еще продолжают меня беспокоить! Ниже приведен пример кода с другой ошибкой сериализации. Я думаю, что происходит следующее: ClassA начинает десериализацию, видит, что у него есть ClassB для десериализации, и ПЕРЕД десериализацией своего идентификатора он начинает десериализацию ClassB. B начинает десериализацию и видит, что у него есть набор ClassA. Экземпляр ClassA частично десериализуется, но даже несмотря на то, что ClassB добавляет его в набор (используя отсутствующий идентификатор ClassA), завершает десериализацию, ClassA затем завершается, и возникает ошибка.

Что я могу сделать, чтобы решить эту проблему ?! Циклические зависимости — очень распространенная практика в гибернации, и я просто не могу смириться с тем, что я единственный, у кого есть эта проблема.

Другим возможным решением является наличие выделенной переменной для хэш-кода (будет вычисляться по идентификатору объекта) и убедитесь (просмотр readObject и writeObject), что она будет прочитана ПЕРЕД САМЫМ ДРУГИМ ОБЪЕКТОМ. Что вы думаете? Есть ли какие-либо недостатки у этого решения?

Пример кода:

 import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

public class Test implements Serializable
{
    public static void main(String[] args) throws Exception
    {
        ClassA aClass = new ClassA();
        aClass.setId(Long.valueOf(321));

        ClassB bClass = new ClassB();
        bClass.setId(Long.valueOf(921));

        Set<ClassA> set = new HashSet<ClassA>();
        set.add(aClass);

        bClass.setSetfield(set);
        aClass.setBField(bClass);

        Set<ClassA> goodClassA = aClass.getBField().getSetfield();
        Set<ClassA> badClassA = serializeAndDeserialize(aClass).getBField().getSetfield();

        System.out.println("Does it contain its member? (should return true!) "   goodClassA.contains(goodClassA.toArray()[0]));
        System.out.println("Does it contain its member? (should return true!) "   badClassA.contains(badClassA.toArray()[0]));
    }

    public static ClassA serializeAndDeserialize(ClassA s) throws Exception
    {
        new ObjectOutputStream(new FileOutputStream(new File("temp"))).writeObject(s);
        return (ClassA) new ObjectInputStream(new FileInputStream(new File("temp"))).readObject();
    }

    public static class ClassB implements Serializable
    {
        private Long mId;
        private Set<ClassA> mSetfield = new HashSet<ClassA>();
        public Long getmId() {
            return mId;
        }
        public void setId(Long mId) {
            this.mId = mId;
        }
        public Set<ClassA> getSetfield() {
            return mSetfield;
        }
        public void setSetfield(Set<ClassA> mSetfield) {
            this.mSetfield = mSetfield;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result   ((mId == null) ? 0 : mId.hashCode());
            return resu<
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            ClassB other = (ClassB) obj;
            if (mId == null) {
                if (other.mId != null)
                    return false;
            } else if (!mId.equals(other.mId))
                return false;
            return true;
        }       
    }

    public static class ClassA implements Serializable
    {
        private Long mId;
        private ClassB mBField;
        public Long getmId() {
            return mId;
        }
        public void setId(Long mId) {
            this.mId = mId;
        }
        public ClassB getBField() {
            return mBField;
        }
        public void setBField(ClassB mBField) {
            this.mBField = mBField;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result   ((mId == null) ? 0 : mId.hashCode());
            return resu<
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            ClassA other = (ClassA) obj;
            if (mId == null) {
                if (other.mId != null)
                    return false;
            } else if (!mId.equals(other.mId))
                return false;
            return true;
        }
    }
}
 

Комментарии:

1. Что, если вы используете LinkedHashSet ?

2. Пожалуйста, исправьте опечатки. Код не компилируется.

3. Исправить опечатки довольно сложно, поскольку компьютер, на котором я писал код, не подключен к Интернету… Я сделаю все, что смогу.

4. Я исправил опечатки и добавил одну проверку в коде, таким образом, я также добавил одну строку к исходному выводу…

Ответ №1:

Итак, как я прочитал, вы основываете хэш FieldOfSerializableClass -код на родительском объекте. Похоже, это основная причина вашей проблемы и очень сомнительный дизайн. hashCode() и equals() метод имеет дело с идентификатором объекта и вообще не должен быть связан с тем, какой родительский элемент их содержит. Идея о том, что идентификатор объекта изменяется в зависимости от того, какому родительскому объекту он принадлежит, по крайней мере, мне очень чужда и является основной причиной, по которой ваш код не работает.

Хотя в других ответах есть несколько способов обойти проблему, я думаю, что самый простой способ исправить это — присвоить FieldOfSerializableClass классу его собственную идентичность. Вы могли бы скопировать mHashCodeField из SerializableClass в FieldOfSerializableClass . Когда родительский объект установлен для объекта, вы можете взять его mHashCodeField и сохранить локально.

 public void setParentLink(SerializableClass pParentLink) {
    this.mHashCodeField = pParentLink.mHashCodeField;
    mParentLink = pParentLink;
}
 

Тогда метод hashcode (и equals) выглядит аналогично методу for SerializableClass .

 @Override
public int hashCode() {
    return ((mHashCodeField == null) ? 0 : mHashCodeField.hashCode());
}
 

Но на самом деле вам следует подумать об изменении кода, чтобы родительские отношения были менее связанными. Подумайте на секунду, что произойдет, если вы вызовете setParentLink() поле, когда оно уже находится в другом SerializableClass наборе. Внезапно исходный класс даже не может найти элемент в своем наборе, поскольку его идентификатор изменился. Присвоение некоторого идентификатора сортировки FieldOfSerializableClass классу, уникальному для родительского класса, является лучшим шаблоном здесь с точки зрения объектов Java.

Вы могли бы использовать UUID.randomUUID() или какой-то статический AtomicInteger класс, который каждый раз выдает новый идентификатор, если вы не можете использовать другие поля в FieldOfSerializableClass качестве правильного идентификатора. Но я бы использовал автоматически сгенерированный идентификатор, предоставленный вам из Hibernate. Вам просто нужно убедиться, что объект был вставлен в базу данных, прежде чем он будет помещен в коллекцию другого объекта.

Комментарии:

1. Я знаю, что объединение объектов не является хорошей практикой (если не сказать больше!), Но приведенная выше ситуация — это та, которая правильно представляет БД. Рассмотрим курс и профессора. Один и тот же курс (например, «Продвинутая Java») может преподаваться одновременно в течение семестра с разными преподавателями. Преподаватель является жизненно важным ключом к курсу (скажем, ключ преподавателя и название курса). Конечно, я могу присвоить курсу уникальный идентификатор (и я это делаю), но, как упоминалось ранее, это не очень хорошая практика в режиме гибернации.

2. Профессор может быть «жизненно важным» для курса, но это не должно влиять на идентичность курса. Возможно, профессор Смит преподает пятничный продвинутый курс Java, на который люди подписываются. Если он переключится на тот, который во вторник, тогда люди, которые зарегистрировались, не будут немедленно переведены во вторник. Изменения профессора не должны влиять на класс. Но на самом деле, это метафора, и мы говорим о шаблонах проектирования Java. Принадлежность объекта не должна влиять на идентификаторы.

3. С точки зрения hibernate, не желающего использовать идентификатор в качестве хэш-кода, это применимо только в том случае, если объект не был вставлен в базу данных. Вы создаете курс в базе данных, прежде чем сможете добавить его в коллекцию профессора. Я не вижу проблемы. docs.jboss.org/hibernate/stable/core/reference/en-US/html /…

Ответ №2:

Десериализация считывает значения обоих полей ( mHashCodeField и mSomeSet ) во временный массив и после десериализации обоих значений присваивает полям сохраненные значения.

Поскольку HashSet пересчитывает хэш-коды своих элементов во время десериализации, он будет использоваться mHashCodeField , когда он все еще равен нулю.

Возможное решение — пометить mSomeSet как переходный и записать / прочитать его в writeObject / readObject .

 @SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
{
    System.out.println("Just started deserializing");
    in.defaultReadObject();
    mSomeSet=(Set<FieldOfSerializableClass>)in.readObject();
    System.out.println("Just finished deserializing");        
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
    System.out.println("Just started serializing");
    out.defaultWriteObject();
    out.writeObject(mSomeSet);
    System.out.println("Just finished serializing");        
}
 

Комментарии:

1. Пометка mHashCodeField как transient не приведет к итерации с hibernate?

Ответ №3:

Это метод equals, который должен быть рефлексивным, транзитивным и симметричным…

Метод hashCode должен обладать следующими свойствами:

Общий контракт hashCode:

Всякий раз, когда он вызывается для одного и того же объекта более одного раза во время выполнения Java-приложения, метод hashCode должен последовательно возвращать одно и то же целое число, при условии, что информация, используемая в сравнениях equals для объекта, не изменяется. Это целое число не обязательно должно оставаться согласованным от одного выполнения приложения до другого выполнения того же приложения.

Если два объекта равны в соответствии с методом equals(Object), то вызов метода hashCode для каждого из двух объектов должен приводить к одному и тому же целочисленному результату.

Не требуется, чтобы, если два объекта неравны в соответствии с equals(java.lang.Object) метод, затем вызов метода hashCode для каждого из двух объектов должен приводить к различным целочисленным результатам. Однако программист должен знать, что получение различных целочисленных результатов для неравных объектов может повысить производительность хэш-таблиц.

Здесь похоже, что хэш-код, используемый для помещения записи в набор во время десериализации, отличается от того, который вычисляется во время contains() . Кстати, как вы заметили, запись находится в наборе, вы просто не можете получить к ней доступ через ее хэш-код, если вы перебираете содержимое набора, вы найдете элементы.

Возможные решения:

  • имейте хэш-код, не полагающийся на родительский объект.
  • используйте структуру данных, которая не использует хэш-код (список, набор деревьев …)
  • не используйте метод contains для набора…
  • реализуйте readResolve для воссоздания набора после десириализации…

[РЕДАКТИРОВАТЬ]: похоже, вы не одиноки bug_id=4957674

Комментарии:

1. Мой хэш-код обладает этими свойствами (плюс его рефлексивный и т. Д.). Проблема возникает только ВО ВРЕМЯ СЕРИЛИЗАЦИИ, поскольку mHashCodeField не серилизуется в правильном порядке (ПОСЛЕ сериализации хэш-код в порядке).

2. Фактически журнал, который вы записываете в методе readObject, записывается во время десериализации, а не во время сериализации…

3. Да, конечно, я немного запутался, но смысл тот же — проблема с хэш-кодом возникает в очень определенное время в приложении. В остальном, с любой другой точки зрения, это написано просто отлично.

4. Что касается вашего редактирования — приятно знать, что я не одинок 🙂 Я не видел там никаких хороших обходных путей, кроме (возможно) одного — изменения readResolve для hashmap, чтобы он не пересчитывал хэш-код объектов при сериализации, вместо этого сохраняя значения хэш-кода before , что вызывает вопрос — почему HashMap пересчитывает карту при десериализации?

Ответ №4:

Я добавляю еще один ответ, потому что он сильно отличается от моего первого:

Вот реализация, которая работает без переходного поля, я нашел необходимую информацию здесь: Расширенная сериализация и здесь .

Кстати, я также пытался использовать serialPersistentFields атрибут для принудительной сериализации mHashCodeFields, но это не помогло…

     public static class SerializableClass implements Serializable {

    // this tells the serialization mechanism to serialize only mHasCodeField...
    private final static ObjectStreamField[]
            serialPersistentFields = {
              new ObjectStreamField(
              "mHashCodeField", String.class)
            };


    private String mHashCodeField;
    private Set<FieldOfSerializableClass> mSomeSet;


    public void setSomeSet(Set<FieldOfSerializableClass> pSomeSet) {
        mSomeSet = pSomeSet;
    }

    public Set<FieldOfSerializableClass> getSomeSet() {
        return mSomeSet;
    }

    public void setHashCodeField(String pHashCodeField) {
        mHashCodeField = pHashCodeField;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;

        System.out.println("In hashCode - value of mHashCodeField: "
                  mHashCodeField);

        result = prime
                * result
                  ((mHashCodeField == null) ? 0 : mHashCodeField.hashCode());
        return resu<
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SerializableClass other = (SerializableClass) obj;

        if (mHashCodeField == null) {
            if (other.mHashCodeField != null) {
                return false;
            }
        } else if (!mHashCodeField.equals(other.mHashCodeField))
            return false;
        return true;
    }

    private void writeObject(java.io.ObjectOutputStream out)
            throws IOException, ClassNotFoundException {
        System.out.println("Just started serializing");
        out.defaultWriteObject();
        out.writeObject(mSomeSet);


        System.out.println("In writeObject - value of mHashCodeField: "
                  mHashCodeField);
        System.out.println("Just finished serializing");
    }

    private void readObject(java.io.ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        System.out.println("Just started deserializing");
        in.defaultReadObject();
        mSomeSet=(Set<FieldOfSerializableClass>)in.readObject();

        System.out.println("In readObject - value of mHashCodeField: "
                  mHashCodeField);
        System.out.println("Just finished deserializing");
    }
}
 

Комментарии:

1. Есть ли способ обобщить это решение? Реализация этого для всех проблемных объектов будет слишком сложной и трудоемкой.

2. Использование списка вместо набора не вариант?

3. Я рассмотрел это, и это может быть применимо к большинству случаев, но HashMap (а не set) жизненно важен для некоторых объектов.

Ответ №5:

Действительно, Hibernate говорит не использовать идентификатор в качестве хэш-кода, но я считаю, что они слишком строги в этом отношении. Это имеет смысл, только если идентификатор автоматически генерируется / автоматически вводится Hibernate. В этом случае у вас может быть компонент, который получает свое значение id только тогда, когда Hibernate решает фактически сохранить его в базе данных, поэтому в этой ситуации вы можете получить непредсказуемое поведение от метода hashcode и / или equals, который использует идентификатор. Однако, если идентификатор задан вручную, т. Е. Ваше приложение имеет дело с заполнением этого значения, тогда я считаю, что вполне нормально использовать его в ваших методах hashcode / equals. Так ли это для вас?

Комментарии:

1. В моем случае идентификатор автоматически генерируется и увеличивается на БД, в которую я верю. Теперь — запрещает ли hibernate использовать ID в hashcode или equals ? Я хочу использовать идентификатор только для хэш-кода — equals все равно будет работать с уникальным идентификатором. Будут ли проблемы с такой настройкой?

2. Само по себе это не запрещено, Hibernate просто «использует» это. Статья здесь: community.jboss.org/wiki/EqualsAndHashCode , может быть, это поможет вам определиться.

3. Я прочитал статью — мне кажется, что действительно использование идентификатора вызывает проблемы только при использовании equals() . hashCode() используется для определения, в какое ведро будет вставлена запись. Даже если все мои объекты использовали ‘0’ в качестве своего хэш-кода, худшим, что может произойти, будет снижение производительности в хэш-таблицах, но объект все равно будет восстановлен правильно. Я ошибаюсь?

4. Да, это теоретически верно, с должным необходимым замечанием, что если ваш хэш-код реализован таким образом, что он возвращает 0 для любого экземпляра, при использовании коллекции, подобной таблице, вы получите производительность связанного списка.

Ответ №6:

Мне кажется, это ошибка в java, а не в вашем исходном коде. Хотя приведенные выше ответы дают хорошие варианты обхода, лучшим решением для Java было бы исправить, как работает десериализация, для учета циклических ссылок и наборов / хэш-карт.

Смотрите Здесь для создания нового отчета об ошибке: http://bugreport.sun.com/bugreport /

Чем больше людей сообщат об этой ошибке, тем больше шансов, что они ее исправят. Я тоже получаю эту ошибку в своем проекте, и обходные пути требуют гораздо больше усилий, чем они того стоят для меня.

Кроме того, вот аналогичное сообщение об ошибке, которое я нашел: http://bugs.sun.com/view_bug.do ;jsessionid=fb27da16bb769ffffffffebce29d31b2574e?bug_id=6208166

Комментарии:

1. Я не думаю, что они когда-либо исправят это — фактически, Джош Блох представил эту ошибку как головоломку в своей книге Java Puzzels (P91), где он заявил, что это, вероятно, никогда не будет исправлено.

Ответ №7:

Я столкнулся с той же проблемой. Я думаю, что вы правы в своем втором редактировании о причине. Вот моя самая простая репликация проблемы:

 public class Test {

    static class Thing implements Serializable {
        String name;
        Set<Thing> others = new HashSet<Thing>();

        @Override
        public int hashCode() {
            if (name == null) {
                System.out.println("hashcode called with null name!");
            }
            return name == null ? 0 : name.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            return o instanceof Thing amp;amp; ((Thing) o).name == name;
        }
    }

    @org.junit.Test
    public void testHashSetCircularDependencySerialization() throws Exception {
        Thing thing = new Thing();
        thing.name = "thing";
        Thing thing2 = new Thing();
        thing2.name = "thing2";
        thing.others.add(thing2);
        thing2.others.add(thing);
        assertTrue(thing2.others.contains(thing));
        Thing thingCopy = (Thing) serializeAndDeserialize(thing);
        Thing thing2Copy = thingCopy.others.iterator().next();
        assertTrue(thing2Copy.others.contains(thingCopy));
    }

    public static Object serializeAndDeserialize(Object other) throws Exception {
        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        new ObjectOutputStream(byteOutputStream).writeObject(other);
        ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteOutputStream.toByteArray());
        return new ObjectInputStream(byteInputStream).readObject();
    }
}
 

Вывод:

 hashcode called with null name!
 

Этот тест завершается неудачей. Самым простым решением, которое я нашел, было сохранить копию хэш-кода. Поскольку это первичный параметр, он устанавливается при инициализации объекта во время десериализации, а не позже:

     int hashcode;

    @Override
    public int hashCode() {
        if (hashcode != 0) {
            return hashcode;
        }
        hashcode = name == null ? 0 : name.hashCode();
        return hashcode;
    }
 

Теперь тест пройден.

Ответ №8:

JDK-4957674: (coll) Хэш-записи, помещенные в неправильные сегменты во время десериализации

При десериализации хэш-карты метод readObject() считывает пары ключ-значение и повторно хэширует карту, вызывая hashCode() для ключей.

Но если реализация ключей hashCode() зависит от некоторой внутренней переменной ключа, и если эта переменная еще не была десериализована в этот момент, то hashCode() выдаст неверный результат, отправив ключ в неправильный хэш-сегмент.

Обходной путь:

 private void writeObject(java.io.ObjectOutputStream out) throws IOException {
    ...
    out.writeObject(new ArrayList<>(theSet));
}

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
    ...
    theSet = new HashSet<>((ArrayList) in.readObject());
}