Сериализация Java: readObject() vs. readResolve() - программирование
Подтвердить что ты не робот

Сериализация Java: readObject() vs. readResolve()

Книга Эффективная Java и другие источники предоставляют довольно хорошее объяснение того, как и когда использовать метод readObject() при работе с сериализуемыми Java-классами. Метод readResolve(), с другой стороны, остается загадкой. В основном все документы, которые я нашел, либо упоминают только один из двух, либо упоминают оба только индивидуально.

Вопросы, которые остаются без ответа, следующие:

  • В чем разница между этими двумя методами?
  • Когда должен быть реализован этот метод?
  • Как следует использовать readResolve(), особенно с точки зрения возврата чего?

Надеюсь, вы сможете пролить свет на этот вопрос.

4b9b3361

Ответ 1

readResolve используется для замены объекта, считанного из потока. Единственное, что я когда-либо видел для этого, это принудительное использование синглетонов; когда объект читается, замените его экземпляром singleton. Это гарантирует, что никто не сможет создать другой экземпляр путем сериализации и десериализации синглтона.

Ответ 2

Метод readResolve вызывается, когда ObjectInputStream читает объект из потока и готовится вернуть его вызывающему. ObjectInputStream проверяет, определяет ли класс объекта метод readResolve. Если метод определен, вызывается метод readResolve, чтобы позволить объекту в потоке назначать возвращаемый объект. Возвращаемый объект должен быть типа, совместимого со всеми видами использования. Если он несовместим, при обнаружении несоответствия типов будет выбрано ClassCastException.

Ответ 3

Item 90, Effective Java, 3rd Ed охватывает readResolve и writeReplace для последовательных прокси - их основное использование. Примеры не записывают методы readObject и writeObject потому что они используют сериализацию по умолчанию для чтения и записи полей.

readResolve вызывается после readObject (и наоборот, writeReplace вызывается до writeObject и, возможно, для другого объекта). Объект, который возвращает метод, заменяет this объект, возвращенный пользователю ObjectInputStream.readObject и любые дальнейшие обратные ссылки на объект в потоке. И readResolve и writeReplace могут возвращать объекты одного и того же или разных типов. Возвращение того же типа полезно в некоторых случаях, когда поля должны быть final и требуется либо обратная совместимость, либо значения должны быть скопированы и/или проверены.

Использование readResolve не применяет свойство singleton.

Ответ 4

readResolve может использоваться для изменения данных, которые сериализуются с помощью метода readObject. Например, xstream API использует эту функцию для инициализации некоторых атрибутов, которые не были в XML для десериализации.

http://x-stream.github.io/faq.html#Serialization

Ответ 5

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

Ответ 6

readObject() - это существующий метод в ObjectInputStream class.one, в то время как объект чтения во время десериализации метод readObject внутренне проверяет, существует ли объект класса, который десериализован, с методом readResolve, или нет, если метод readResolve существует, тогда он вызовет readResolve и вернуть тот же экземпляр.

Таким образом, цель написания метода readResolve - это хорошая практика для достижения чистого одноэлементного шаблона проектирования, где никто не может получить другой экземпляр путем сериализации/десериализации.

Ответ 7

readResolve() будет обеспечивать одноконтактный контракт при сериализации.
Пожалуйста, обратитесь

Ответ 8

Когда сериализация используется для преобразования объекта, чтобы он мог быть сохранен в файле, мы можем вызвать метод readResolve(). Метод является закрытым и хранится в том же классе, объект которого извлекается во время десериализации. Это гарантирует, что после десериализации, какой объект возвращается, тот же, что и сериализован. То есть instanceSer.hashCode() == instanceDeSer.hashCode()

readResolve() метод не является статическим методом. После того, как in.readObject() вызывается при десериализации, он просто гарантирует, что возвращаемый объект будет таким же, как тот, который был сериализован, как показано ниже, а out.writeObject(instanceSer)

..
    ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file1.ser"));
    out.writeObject(instanceSer);
    out.close();

Таким образом, он также помогает в реализации singleton design pattern, поскольку каждый раз возвращается один и тот же экземпляр.

public static ABCSingleton getInstance(){
    return ABCSingleton.instance; //instance is static 
}

Ответ 9

Метод readResolve

Для классов Serializable и Externalizable метод readResolve позволяет классу заменять/разрешать объект, считанный из потока, прежде чем он будет возвращен вызывающему. Внедряя метод readResolve, класс может напрямую контролировать типы и экземпляры своих дескрипторов своих экземпляров. Метод определяется следующим образом:

ANY-ACCESS-MODIFIER Объект readResolve()           бросает ObjectStreamException;

Метод readResolve вызывается, когда ObjectInputStream читает объект из потока и готовится вернуть его вызывающему. ObjectInputStream проверяет, определяет ли класс объекта метод readResolve. Если метод определен, вызывается метод readResolve, позволяющий объекту в потоке назначать возвращаемый объект. Возвращаемый объект должен быть типа, совместимого со всеми видами использования. Если это несовместимо, ClassCastException будет вызываться, когда обнаружено несоответствие типа.

Например, может быть создан класс Символ, для которого в виртуальной машине существует только один экземпляр каждой привязки символов. Метод readResolve будет реализован, чтобы определить, был ли этот символ уже определен и заменить существующий эквивалентный объект Symbol, чтобы сохранить ограничение идентификации. Таким образом, уникальность объектов Symbol может поддерживаться через сериализацию.

Ответ 10

Как уже было readResolve, readResolve - это закрытый метод, используемый в ObjectInputStream при десериализации объекта. Это вызывается как раз перед тем, как фактический экземпляр возвращается. В случае Singleton здесь мы можем принудительно вернуть уже существующую ссылку на экземпляр Singleton вместо десериализованной ссылки на экземпляр. Аналогично, у нас есть writeReplace для writeReplace.

Пример для readResolve:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SingletonWithSerializable implements Serializable {
private static final long serialVersionUID = 1L;

public static final SingletonWithSerializable INSTANCE = new SingletonWithSerializable();

private SingletonWithSerializable() {
    if (INSTANCE != null)
        throw new RuntimeException("Singleton instance already exists!");
}

private Object readResolve() {
    return INSTANCE;
}

public void leaveTheBuilding() {
    System.out.println("SingletonWithPublicFinalField.leaveTheBuilding() called...");
}

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    SingletonWithSerializable instance = SingletonWithSerializable.INSTANCE;

    System.out.println("Before serialization: " + instance);

    try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file1.ser"))) {
        out.writeObject(instance);
    }

    try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("file1.ser"))) {
        SingletonWithSerializable readObject = (SingletonWithSerializable) in.readObject();
        System.out.println("After deserialization: " + readObject);
    }

}

}

Выход:

Before serialization: [email protected]
After deserialization: [email protected]

Ответ 11

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

Взять, к примеру, коллекционные классы. Сериализация по умолчанию связанного списка или BST приведет к огромной потере пространства с очень небольшим выигрышем в производительности по сравнению с простой сериализацией элементов по порядку. Это еще более верно, если коллекция является проекцией или представлением - сохраняет ссылку на более крупную структуру, чем она предоставляет в своем открытом API.

  1. Если сериализованный объект имеет неизменяемые поля, которые требуют настраиваемой сериализации, оригинального решения writeObject/readObject недостаточно, поскольку десериализованный объект создается перед чтением части потока, записанной в writeObject. Возьмите эту минимальную реализацию связанного списка:

    public class List<E> extends Serializable {
        public final E head;
        public final List<E> tail;
    
        public List(E head, List<E> tail) {
            if (head==null)
                throw new IllegalArgumentException("null as a list element");
            this.head = head;
            this.tail = tail;
        }
    
        //methods follow...
    }
    

Эта структура может быть сериализовать путем рекурсивного записи head поле каждого звена, за которым следует null значение. Однако десериализация такого формата становится невозможной: readObject не может изменить значения полей-членов (теперь они установлены на null). А вот и writeReplace/readResolve:

private Object writeReplace() {
    return new Serializable() {
        private transient List<E> contents = List.this;

        private void writeObject(ObjectOutputStream oos) {
            List<E> list = contents;
            while (list!=null) {
                oos.writeObject(list.head);
                list = list.tail;
            }
            oos.writeObject(null);
        }

        private void readObject(ObjectInputStream ois) {
            List<E> tail = null;
            E head = ois.readObject();
            if (head!=null) {
                readObject(ois); //read the tail and assign it to this.contents
                this.contents = new List<>(head, this.contents)
            }                     
        }


        private Object readResolve() {
            return this.contents;
        }
    }
}

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

  1. Мы можем захотеть фактически десериализовать объект другого класса, чем мы написали в ObjectOutputStream. Это может быть в случае с представлениями, такими как реализация списка java.util.List которая предоставляет фрагмент из более длинного ArrayList. Очевидно, что сериализация всего списка поддержки - плохая идея, и мы должны писать только элементы из просматриваемого фрагмента. Однако зачем останавливаться на этом и иметь бесполезный уровень косвенности после десериализации? Мы могли бы просто прочитать элементы из потока в ArrayList и вернуть его напрямую, вместо того, чтобы помещать его в наш класс представления.

  2. Альтернативно, наличие подобного класса делегата, выделенного для сериализации, может быть выбором дизайна. Хорошим примером будет повторное использование нашего кода сериализации. Например, если у нас есть класс построителя (похожий на StringBuilder для String), мы можем написать делегат сериализации, который сериализует любую коллекцию, записав пустой поток в поток, за которым следуют размер коллекции и элементы, возвращаемые итератором colection. Десериализация будет включать чтение компоновщика, добавление всех последующих элементов чтения и возврат результата final build() от делегатов readResolve. В этом случае нам потребуется реализовать сериализацию только в корневом классе иерархии коллекций, и не потребуется никакого дополнительного кода из текущих или будущих реализаций, при условии, что они реализуют абстрактный iterator() и метод builder() (последний для воссоздания). коллекция того же типа - которая была бы очень полезной функцией сама по себе). Другим примером может быть иерархия классов, код которой мы не полностью контролируем - наш базовый класс из сторонней библиотеки может иметь любое количество закрытых полей, о которых мы ничего не знаем, и которые могут меняться от одной версии к другой, нарушая наши сериализованные объекты. В этом случае было бы безопаснее записать данные и пересобрать объект вручную при десериализации.