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


127

Книга Ефективна Java та інші джерела дають досить хороше пояснення того, як і коли використовувати метод readObject () під час роботи з серіалізаційними класами Java. З іншого боку, метод readResolve () залишається трохи таємницею. В основному всі документи, які я знайшов, або згадують лише один із двох, або обоє згадують лише окремо.

Питання, які не відповідають на це, є:

  • У чому різниця між двома методами?
  • Коли слід застосовувати який метод?
  • Як слід читати Reesolve (), особливо з точки зору повернення чого?

Я сподіваюся, що ви можете пролити трохи світла в цьому питанні.


Приклад з JDK Oracle:String.CaseInsensitiveComparator.readResolve()
kevinarpe

Відповіді:


138

readResolveвикористовується для заміни об'єкта, прочитаного з потоку. Єдине використання, яке я коли-небудь бачив для цього, - це застосування синглів; коли об'єкт читається, замініть його на одиночний екземпляр. Це гарантує, що ніхто не може створити інший екземпляр шляхом серіалізації та десеріалізації сінгтона.


3
Існує ряд способів, як шкідливий код (або навіть дані) обійти це.
Том Хотін - тайклін

6
Джош Блох розповідає про умови, за яких це порушує ефективну програму Java 2nd ed. Пункт 77. Він згадує про це в цій розмові, яку він виголосив у Google IO пару років тому (кілька разів до кінця розмови): youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy

17
Я вважаю цю відповідь трохи неадекватною, оскільки вона не згадує transientполя. readResolveвикористовується для вирішення об'єкта після його зчитування. Приклад використання, можливо, об'єкт містить деякий кеш, який можна відтворити з існуючих даних і не потребує серіалізації; кешовані дані можуть бути оголошені transientта readResolve()можуть відновити їх після десеріалізації. Такі речі є для чого цей метод.
Джейсон C

2
@JasonC ваш коментар , що « такі речі , як , що [транзиторною обробка] то , що цей метод для " вводить в оману. Див. Документ Java для Serializable: він говорить "Класи, яким потрібно призначити заміну, коли екземпляр її читається з потоку, повинен реалізувати цей [ readResolve] спеціальний метод ...".
Офер

2
Метод readResolve також може бути використаний у кутовому випадку, де, припустимо, ви серіалізували багато об’єктів і зберігали їх у базі даних. Якщо пізніше ви захочете перемістити ці дані у новий формат, ви можете легко досягти цього в методі readResolve.
Нілеш Раджані

29

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

readResolveвикликається після readObjectповернувся (навпаки writeReplace, називається раніше writeObjectі, ймовірно, на іншому об'єкті). Об'єкт, який повертає метод, замінює thisоб'єкт, повернутий користувачеві, ObjectInputStream.readObjectта будь-які подальші зворотні посилання на об'єкт у потоці. Обидва readResolveі writeReplaceможуть повертати об'єкти одного або різних типів. Повернення одного типу корисне в деяких випадках, коли поля повинні бути, finalабо потрібна або зворотна сумісність, або значення повинні бути скопійовані та / або підтверджені.

Використання readResolveне примушує властивості singleton.


9

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

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


1
XML та Xstream не стосуються питання про серіалізацію Java, і на це питання відповіли правильно років тому. -1
Маркіз Лорнський

5
У прийнятій відповіді зазначено, що readResolve використовується для заміни об'єкта. Ця відповідь надає корисну додаткову інформацію про те, що вона може бути використана для модифікації об'єкта під час десеріалізації. XStream був поданий як приклад, а не як єдина можлива бібліотека, в якій це відбувається.
Переглянув

5

readResolve призначений для того, коли вам може знадобитися повернути існуючий об’єкт, наприклад, тому, що ви перевіряєте наявність дублікатів входів, які слід об'єднати, або (наприклад, у злагоджених розподілених системах), тому що це оновлення, яке може надійти до того, як вам відомо будь-які старіші версії.


readResolve () було для мене зрозумілим, але все ж у мене є деякі незрозумілі запитання на увазі, але твою відповідь просто прочитав мій погляд, дякую
Ражні Гангвар

5

readObject () являє собою існуючий метод в ObjectInputStream class.while читання об'єкта під час десеріалізациі методи readObject внутрішньо перевірити , є чи об'єкт класу , який в даний час десеріалізациі , мають метод readResolve чи ні , якщо метод readResolve існує , то він буде викликати метод readResolve і повернутися в той же екземпляр.

Таким чином, намір написати метод readResolve є хорошою практикою для досягнення чистої однотонної схеми дизайну, де ніхто не може отримати інший екземпляр шляхом серіалізації / дезаріалізації.



2

Коли серіалізація використовується для перетворення об’єкта, щоб він міг бути збережений у файл, ми можемо запустити метод readResolve (). Метод приватний і зберігається в тому ж класі, об’єкт якого витягується під час десеріалізації. Це гарантує, що після десеріалізації повернутий об’єкт буде таким самим, як був серіалізований. Це є,instanceSer.hashCode() == instanceDeSer.hashCode()

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

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

Таким чином, він також допомагає в реалізації однотонної схеми дизайну , тому що кожен раз повертається один і той же екземпляр.

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

1

Я знаю, що це запитання справді старе і має прийняту відповідь, але, оскільки воно з'являється дуже високо в пошуку в 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), ми можемо написати делегата серіалізації, який серіалізує будь-яку колекцію, записавши порожній конструктор в потік, а потім розмір колекції та елементи, повернуті ітератором колекції. Десеріалізація передбачає читання будівельника, додавання всіх згодом прочитаних елементів та повернення результату фіналу build()від делегатів readResolve. У такому випадку нам знадобиться реалізувати серіалізацію лише в кореневому класі ієрархії колекції, і жоден додатковий код не буде потрібний у поточних чи майбутніх реалізаціях, за умови, що вони реалізують абстрактні iterator()таbuilder()метод (останній для відтворення колекції одного типу - що було б самою корисною ознакою). Іншим прикладом може бути ієрархія класів, кодом якого ми не повністю керуємо - наш базовий клас із сторонньої бібліотеки може мати будь-яку кількість приватних полів, про які ми нічого не знаємо і які можуть змінюватися від однієї версії до іншої, порушуючи наші серіалізовані об’єкти. У такому випадку було б безпечніше записати дані та відновити об’єкт вручну після десеріалізації.


0

Метод readResolve

Для класів Serializable та Externalizable метод readResolve дозволяє класу замінити / вирішити прочитаний об'єкт із потоку перед тим, як його повернути до абонента. Реалізуючи метод readResolve, клас може безпосередньо керувати типами та екземплярами десеріалізованих власних екземплярів. Метод визначається наступним чином:

ANY-ACCESS-MODIFIER Об'єкт readResolve () кидає ObjectStreamException;

Метод readResolve викликається, коли ObjectInputStream прочитав об'єкт із потоку і готується повернути його абоненту. ObjectInputStream перевіряє, чи визначає клас об'єкта метод readResolve. Якщо метод визначений, викликається метод readResolve, щоб дозволити об'єкту в потоці призначити повернення об'єкта. Об'єкт, що повертається, повинен бути такого типу, який сумісний із усіма напрямами. Якщо вона не сумісна, ClassCastException буде викинуто, коли буде виявлено невідповідність типу.

Наприклад, може бути створений клас Symbol, для якого у віртуальній машині існував лише один екземпляр кожного прив'язки символів. Метод readResolve буде реалізований, щоб визначити, чи був цей символ уже визначений, і замінити попередньо існуючий еквівалентний об'єкт Symbol для підтримки обмеження ідентичності. Таким чином можна зберігати унікальність об'єктів Symbol через серіалізацію.


0

Як уже відповіли, readResolveце приватний метод, який використовується в ObjectInputStream під час десеріалізації об'єкта. Це викликається перед поверненням фактичного примірника. У випадку Singleton, тут ми можемо примусити повернути вже існуючу посилання на екземпляр singleton замість дезаріалізованої посилання на екземпляр. Подібні умови writeReplaceдля ObjectOutputStream.

Приклад для 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: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.