Чому java.util.Options не є серіалізаційним, як серіалізувати об’єкт за допомогою таких полів


107

Клас Enum є серіалізаційним, тому немає проблем із серіалізацією об’єкта за допомогою перерахунків. Інший випадок, коли клас має поля класу java.util.Options. У цьому випадку викидається наступний виняток: java.io.NotSerializableException: java.util.Otional

Як поводитися з такими заняттями, як їх серіалізувати? Чи можна надсилати такі об’єкти на віддалений EJB або через RMI?

Це приклад:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Optional;

import org.junit.Test;

public class SerializationTest {

    static class My implements Serializable {

        private static final long serialVersionUID = 1L;
        Optional<Integer> value = Optional.empty();

        public void setValue(Integer i) {
            this.i = Optional.of(i);
        }

        public Optional<Integer> getValue() {
            return value;
        }
    }

    //java.io.NotSerializableException is thrown

    @Test
    public void serialize() {
        My my = new My();
        byte[] bytes = toBytes(my);
    }

    public static <T extends Serializable> byte[] toBytes(T reportInfo) {
        try (ByteArrayOutputStream bstream = new ByteArrayOutputStream()) {
            try (ObjectOutputStream ostream = new ObjectOutputStream(bstream)) {
                ostream.writeObject(reportInfo);
            }
            return bstream.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Якщо Optionalбуло позначено як " Serializable," що буде, якщо get()повернеться щось, що не може бути серіалізаційним?
ВВ.

10
@WW. Ви отримали б NotSerializableException,звичайно.
Маркіз Лорн

7
@WW. Це просто як колекції. Більшість класів колекції є серіалізаційними, але екземпляр колекції насправді може бути серіалізований лише у тому випадку, якщо кожен об'єкт, що міститься у колекції, також може бути серіалізаційний.
Стюарт Маркс

Моя особиста думка тут: це не нагадувати людям належним чином перевірити навіть серіалізацію об’єктів. Щойно я наткнувся на java.io.NotSerializableException (java.util.O
optional

Відповіді:


171

Ця відповідь відповідає на запитання в заголовку: "Чи не повинен необов'язково бути серіалізаційним?" Коротка відповідь полягає в тому, що експертна група Java Lambda (JSR-335) розглянула та відхилила її . Ця примітка, і ця, і ця вказують на те, що головна мета проектування для Optionalвикористання повинна бути як повернене значення функцій, коли повернене значення може бути відсутнім. Намір полягає в тому, щоб абонент негайно перевірив Optionalі вилучив фактичне значення, якщо воно є. Якщо значення відсутнє, абонент може замінити значення за замовчуванням, викинути виняток або застосувати якусь іншу політику. Зазвичай це робиться за допомогою ланцюга плавного методу, який відключає кінець потокового трубопроводу (або інших методів), який повертає Optionalзначення.

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

Зазвичай існують кращі способи впорядкування даних, ніж зберігання Optionalв полі. Якщо геттер (наприклад, getValueметод у запитанні) повертає фактичне Optionalз поля, він змушує кожного абонента здійснити певну політику щодо роботи з порожнім значенням. Це, ймовірно, призведе до неуважної поведінки між абонентами. Часто краще мати будь-які набори коду для цього поля, застосовуючи певну політику під час встановлення.

Іноді люди хочуть скласти Optionalколекції, як-от List<Optional<X>>або Map<Key,Optional<Value>>. Це теж зазвичай погана ідея. Це часто краще замінити ці звичаї Optionalз Null-об'єктних значень (не фактичнимnull посилання), або просто опустити ці записи з колекції цілком.


26
Молодці Стюарт. Я мушу сказати, що це надзвичайний ланцюжок, якщо міркувати, і це надзвичайні речі, щоб створити клас, який не призначений для використання в якості типу члена екземпляра. Особливо, коли це не вказано в класовому контракті на Javadoc. Можливо, вони мали б створити анотацію замість класу.
Маркіз Лорн

1
Це відмінна публікація в блозі про обґрунтування відповіді Сутарта
Веслі Хартфорд

2
Добре, що мені не потрібно використовувати серіалізаційні поля. Інакше це буде катастрофою
Курру

48
Цікава відповідь, для мене вибір дизайну був абсолютно неприйнятним і неправильним. Ви кажете "Зазвичай є кращі способи впорядкування даних, ніж зберігання необов'язкового в полі", звичайно, може, чому б і ні, але це має бути вибір дизайнера, а не мови. Це ще один із таких випадків, коли я сильно пропускаю необов'язкові параметри Scala на Java (додаткові параметри Scala можна серіалізувати, і вони відповідають правилам Monad)
Гійом

37
"головна мета проектування для Факультативного - використовувати як повернене значення функцій, коли значення, що повертається, може бути відсутнім." Ну, схоже, ви не можете використовувати їх для повернення значень у віддаленому EJB. Чудово ...
Тіло

15

Багато Serializationпов'язаних з цим проблем можна вирішити, від’єднавши стійку серіалізовану форму від реальної реалізації програми, над якою ви працюєте.

/** The class you work with in your runtime */
public class My implements Serializable {
    private static final long serialVersionUID = 1L;

    Optional<Integer> value = Optional.empty();

    public void setValue(Integer i) {
        this.value = Optional.ofNullable(i);
    }

    public Optional<Integer> getValue() {
        return value;
    }
    private Object writeReplace() throws ObjectStreamException
    {
        return new MySerialized(this);
    }
}
/** The persistent representation which exists in bytestreams only */
final class MySerialized implements Serializable {
    private final Integer value;

    MySerialized(My my) {
        value=my.getValue().orElse(null);
    }
    private Object readResolve() throws ObjectStreamException {
        My my=new My();
        my.setValue(value);
        return my;
    }
}

Клас Optional реалізує поведінку, яка дозволяє писати хороший код при роботі з можливо відсутніми значеннями (порівняно з використаннямnull ). Але це не додає ніякої користі для постійного представлення ваших даних. Це просто збільшить ваші серіалізовані дані ...

Ескіз вище може виглядати складним, але це тому, що він демонструє візерунок лише з одним властивістю. Чим більше властивостей має ваш клас, тим більше повинна виявлятись його простота.

І не забувати, можливість повністю змінити реалізацію Myбез необхідності адаптувати стійку форму ...


2
+1, але з більшою кількістю полів буде більше шаблонів для їх копіювання.
Марко Тополник

1
@Marko Topolnik: максимум, один рядок на власність та напрямок. Однак, надаючи відповідний конструктор для class My, який ви зазвичай робите, як це зручно і для інших цілей, readResolveможе бути однолінійною реалізацією, таким чином, зменшивши котлован до однієї лінії на кожну властивість. Що не так багато з огляду на той факт, що кожне змінне властивість має принаймні сім рядків коду в класі My.
Холгер

Я написав пост, що стосувався тієї ж теми. По суті це довга текстова версія цієї відповіді: Серіалізувати необов’язково
Ніколай

Зворотна сторона - це потрібно зробити для будь-якого боба / піхо, який використовує додаткові елементи. Але все-таки приємна ідея.
GhostCat


4

Це цікавий упущення.

Вам слід позначити поле як transientі вказати власний власний writeObject()метод, який сам записав get()результат, і readObject()метод, який відновив Optional, прочитавши цей результат з потоку. Не забуваючи дзвонити defaultWriteObject()і defaultReadObject()відповідно.


Якщо я володію кодом класу, зручніше зберігати простий об’єкт у полі. Необов’язковий клас у такому випадку буде обмежений для інтерфейсу класу (метод get get return Optional.ofNullable (field)). Але для внутрішнього представлення неможливо використовувати необов’язкове для чіткого наміру, що значення є необов’язковим.
vanarchi

Я просто показав , що це можливо. Якщо ти чомусь думаєш інакше, про що саме йдеться у твоєму питанні?
Маркіз Лорн

Дякую за вашу відповідь, це додає для мене варіант. У своєму коментарі я хотів показати подальші думки на цю тему, розглянути питання про протилежність кожного рішення. У використанні методів writeObject / readObject ми маємо чіткий намір Необов’язковий у представленні штату, але реалізація серіалізації ускладнюється. Якщо поле інтенсивно використовується в обчисленні / потоках - зручніше використовувати writeObject / readObject.
vanarchi

Коментарі повинні відповідати відповідям, під якими вони з’являються. Актуальність ваших місінгів відверто уникає мене.
Маркіз Лорн

3

У бібліотеці Vavr.io (колишній Javaslang) також є Optionклас, який можна серіалізувати:

public interface Option<T> extends Value<T>, Serializable { ... }

0

Якщо ви хочете підтримувати більш послідовний список типів і уникати використання нуля, є одна альтернатива kooky.

Можна зберігати значення за допомогою перетину типів . У поєднанні з лямбда, це дозволяє щось подібне:

private final Supplier<Optional<Integer>> suppValue;
....
List<Integer> temp = value
        .map(v -> v.map(Arrays::asList).orElseGet(ArrayList::new))
        .orElse(null);
this.suppValue = (Supplier<Optional<Integer>> & Serializable)() -> temp==null ? Optional.empty() : temp.stream().findFirst();

Наявність tempокремої змінної дозволяє уникнути закриття власника valueчлена і, таким чином, занадто сильно серіалізувати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.