Почему java.util.Optional не является сериализуемым, как сериализовать объект с такими полями


107

Класс Enum является сериализуемым, поэтому нет проблем с сериализацией объекта с помощью перечислений. Другой случай - когда у класса есть поля класса java.util.Optional. В этом случае выдается следующее исключение: java.io.NotSerializableException: java.util.Optional

Как бороться с такими классами, как их сериализовать? Можно ли отправлять такие объекты в Remote 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()возвращено что-то, что нельзя сериализовать?
WW.

10
@WW. Вы, NotSerializableException,конечно, получите .
Marquis of Lorne

7
@WW. Это как коллекции. Большинство классов коллекции являются сериализуемыми, но экземпляр коллекции может быть сериализован только в том случае, если каждый объект, содержащийся в коллекции, также сериализуем.
Стюарт Маркс

Мое личное мнение: это не для того, чтобы напоминать людям о правильном модульном тестировании даже сериализации объектов. Только что столкнулся с java.io.NotSerializableException (java.util.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. Возможно, им следовало разработать аннотацию вместо класса.
Marquis of Lorne

1
Это отличное сообщение в блоге о причинах ответа Сутарта
Уэсли Хартфорд

2
Хорошо, что мне не нужно использовать сериализуемые поля. В противном случае это было бы катастрофой
Курру

48
Интересный ответ: для меня этот выбор дизайна был совершенно неприемлемым и ошибочным. Вы говорите: «Обычно есть лучшие способы организовать данные, чем хранить необязательное в поле», конечно, возможно, почему бы и нет, но это должен быть выбор дизайнера, а не языка. Это еще один из тех случаев, когда я сильно скучаю по опциям Scala в Java (опции Scala являются сериализуемыми, и они следуют рекомендациям Monad)
Гийом

37
«основная цель разработки Optional - использовать его в качестве возвращаемого значения функции, когда возвращаемое значение может отсутствовать». Что ж, похоже, вы не можете использовать их для возвращаемых значений в удаленном 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любом случае имеет не менее семи строк кода в классе .
Holger

Я написал сообщение на ту же тему. По сути, это длинная текстовая версия этого ответа: « Сериализировать по желанию»
Николай

Обратной стороной является то, что вам нужно сделать это для любого bean-компонента / pojo, который использует необязательные параметры. Но все же хорошая идея.
GhostCat


4

Любопытное упущение.

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


Если я владею кодом класса, удобнее хранить в поле простой объект. Необязательный класс в таком случае будет ограничен для интерфейса класса (метод get вернет Optional.ofNullable (field)). Но для внутреннего представления невозможно использовать Optional, чтобы четко обозначить, что значение является необязательным.
vanarchi

Я просто показал , что это возможно. Если вы почему-то думаете иначе, о чем конкретно ваш вопрос?
Marquis of Lorne

Спасибо за ответ, это добавляет мне возможность. В своем комментарии я хотел показать дальнейшие мысли по этой теме, рассмотреть за и против каждого решения. При использовании методов writeObject / readObject у нас есть четкое намерение Optional в представлении состояния, но реализация сериализации становится более сложной. Если поле интенсивно используется в вычислениях / потоках - удобнее использовать writeObject / readObject.
vanarchi

Комментарии должны иметь отношение к ответам, под которыми они появляются. Откровенно говоря, актуальность ваших размышлений ускользает от меня.
Marquis of Lorne

3

В библиотеке Vavr.io (бывший Javaslang) также есть Optionкласс, который можно сериализовать:

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

0

Если вы хотите поддерживать более согласованный список типов и избегать использования null, есть одна странная альтернатива.

Вы можете сохранить значение, используя пересечение типов . В сочетании с лямбдой это позволяет что-то вроде:

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.