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

Класс 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);
        }
    }
}

person vanarchi    schedule 03.07.2014    source источник
comment
Если Optional помечен как Serializable, то что произойдет, если get() вернет что-то, что нельзя сериализовать?   -  person WW.    schedule 03.07.2014
comment
@WW. Конечно, вы получите NotSerializableException,.   -  person user207421    schedule 03.07.2014
comment
@WW. Это как коллекции. Большинство классов коллекций сериализуемы, но на самом деле экземпляр коллекции может быть сериализован только в том случае, если каждый объект, содержащийся в коллекции, также сериализуем.   -  person Stuart Marks    schedule 04.07.2014
comment
Мое личное мнение здесь: не стоит напоминать людям о правильном модульном тестировании даже сериализации объектов. Только что сам столкнулся с java.io.NotSerializableException(java.util.Optional) ;-(   -  person GhostCat    schedule 11.12.2017
comment
Гааааааа. Потерял 3 часа на этом сегодня, и получение сонара для передачи полей в сериализуемом классе должно быть либо временным, либо сериализуемым. rules.sonarsource.com/java/RSPEC-1948   -  person granadaCoder    schedule 09.10.2020


Ответы (6)


Этот ответ является ответом на вопрос в заголовке: «Разве необязательно должен быть сериализуемый?» Короткий ответ: группа экспертов Java Lambda (JSR-335) рассмотрели и отклонили. Это примечание и это и это указывает, что основной дизайн цель для Optional — использовать в качестве возвращаемого значения функций, когда возвращаемое значение может отсутствовать. Цель состоит в том, чтобы вызывающая сторона немедленно проверила Optional и извлекла фактическое значение, если оно присутствует. Если значение отсутствует, вызывающий объект может заменить значение по умолчанию, создать исключение или применить какую-либо другую политику. Обычно это делается путем связывания вызовов плавных методов с конца потокового конвейера (или других методов), которые возвращают Optional значений.

Никогда не предполагалось, что Optional будет использоваться другими способами, например, для дополнительных аргументов метода или как хранится как поле в объекте. Кроме того, создание сериализуемого Optional позволит хранить его постоянно или передавать по сети, что поощряет использование, выходящее далеко за рамки его первоначальной цели.

Обычно есть лучшие способы организовать данные, чем хранить Optional в поле. Если геттер (такой как метод getValue в вопросе) возвращает фактическое Optional из поля, это заставляет каждого вызывающего объекта реализовать некоторую политику для работы с пустым значением. Это, вероятно, приведет к непоследовательному поведению вызывающих абонентов. Часто лучше, чтобы любой код, установленный в этом поле, применял некоторую политику во время ее установки.

Иногда люди хотят поместить Optional в коллекции, например List<Optional<X>> или Map<Key,Optional<Value>>. Это тоже обычно плохая идея. Часто лучше заменить эти варианты использования Optional значениями Null-Object (не фактическими ссылками null), или просто полностью исключить эти записи из коллекции.

person Stuart Marks    schedule 03.07.2014
comment
Молодец Стюарт. Я должен сказать, что это экстраординарная цепочка рассуждений, и это экстраординарные вещи, которые делаются для разработки класса, который не предназначен для использования в качестве типа члена экземпляра. Особенно, когда это не указано в контракте класса в Javadoc. Возможно, им следовало разработать аннотацию вместо класса. - person user207421; 04.07.2014
comment
Это отличный пост в блоге с обоснованием ответа Сутарта: blog.joda.org/2014/11/Optional-in-java-se-8.html - person Wesley Hartford; 30.01.2015
comment
Очень полезная информация. По какой-то причине моим первым побуждением было использовать его как поле в классе. Хорошо, что этого следует избегать. - person Daniel Kaplan; 01.02.2015
comment
Хорошо, что мне не нужно использовать сериализуемые поля. Иначе это была бы катастрофа - person Kurru; 18.07.2015
comment
Optional предоставляет все средства для чистой и безопасной работы с нулевыми элементами, но они говорят, что это не было нашей целью. Что ж, я думаю, что поддержка Optional для членов будет полностью соответствовать «Необязательный параметр добавляется за ценность, которую он предлагает в беглых последовательностях утверждений». - person steffen; 21.08.2015
comment
Интересный ответ, для меня этот выбор дизайна был совершенно неприемлемым и ошибочным. Вы говорите, что обычно есть лучшие способы организовать данные, чем хранить необязательный элемент в поле, конечно, может быть, почему бы и нет, но это должен быть выбор дизайнера, а не языка. Это еще один из тех случаев, когда мне очень не хватает опций Scala в Java (опции Scala сериализуемы и следуют рекомендациям Monad). - person Guillaume; 11.01.2016
comment
первичная цель разработки для Необязательного состоит в том, чтобы использоваться в качестве возвращаемого значения функций, когда возвращаемое значение может отсутствовать. Что ж, похоже, вы не можете использовать их для возвращаемых значений в удаленном EJB. Здорово... - person Thilo; 31.07.2016
comment
Это интересно, так как кажется, что это противоречит книге Java 8 в действии, которую я читаю, где они предлагают использовать Optional для переменных класса в bean-компонентах. Или, может быть, я просто не дочитал до конца, и они пояснят в следующей главе :) - person ToeBee; 17.02.2017
comment
@ToeBee Это в 10.2? Проблема несериализуемости обсуждается на боковой панели в конце раздела 10.3.3. См. также этот обмен сообщениями в Твиттере, который у меня был с одним из авторов после того, как книга была написана (следите за веткой вперед и назад): >twitter.com/stuartmarks/status/784202445102592001 - person Stuart Marks; 17.02.2017
comment
Таким образом, вы держите null или экземпляры в полях и возвращаете Optional из геттеров. Хорошо для типов Object, но в меньшей степени для примитивов, скажем, int против OptionalInt, поскольку нет неуказанного значения для int, если только вы не прибегаете к объектам Integer. - person simon.watts; 31.05.2018
comment
Согласен, это должен быть выбор дизайнера приложения. Что, если я хочу использовать его в службах, которые реализованы как EJB, например. со слоя DAO на уровень репозитория? Поскольку Optional не реализует Serializable, код вызовет исключение ClassCastException. Так случилось со мной в Websphere. - person Spindizzy; 15.06.2018
comment
Что если использовать необязательное поле как часть пользовательского подкласса Exception? Поскольку Exception является сериализуемым, опциональное тоже должно быть. Что делать в таком случае? Нет необязательного в исключении? Использовать нули? - person angelcervera; 22.10.2019
comment
Я не очень понимаю аргументацию здесь. Этот пост основан на идее, что намерения дизайнера библиотеки, стоящие за введением типа, имеют отношение к тому, как этот тип должен использоваться. Я вообще не вижу, чтобы это было так. Изобретатели компьютеров намеревались ускорить некоторые арифметические операции и взломать шифрование, но вот мы здесь, используем его для моделирования физики жидкости, чтобы оптимизировать аэродинамику наших автомобилей. Если меня не интересует механизм сериализации Java (который чертовски сломан), почему бы мне не использовать Optional там, где он мне нужен, например, в поле или параметре? - person Alexander; 29.01.2020
comment
тлдр; программисты используют необязательные в полезных случаях за пределами исходного дизайна, и первоначальным разработчикам необязательных это не нравится. - person Luke; 01.05.2020
comment
Значит, они не хотят сделать необязательный более полезным, чем он был задуман изначально? ИМХО это не обоснование. он заставляет каждого вызывающего абонента реализовывать некоторую политику для работы с пустым значением - это именно то, что я ХОЧУ. - person Sam Stainsby; 10.11.2020
comment
Раньше мы кэшировали результаты с помощью аннотации JSR107 @CacheResult и только что обнаружили, что Hazelcast недоволен несериализуемым опционом. Необязательный используется как тип возвращаемого значения, но, к сожалению, мы не можем кэшировать этот результат. Null — это не решение, потому что это тоже запрещено. - person sgflt; 02.12.2020

Многие проблемы, связанные с 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 без необходимости адаптировать персистентную форму…

person Holger    schedule 03.07.2014
comment
+1, но с большим количеством полей будет больше шаблонов для их копирования. - person Marko Topolnik; 03.07.2014
comment
@Марко Топольник: не более одной строки на свойство и направление. Однако, предоставив соответствующий конструктор для class My, который вы обычно делаете, так как он удобен и для других целей, readResolve может быть однострочной реализацией, таким образом уменьшая шаблон до одной строки для каждого свойства. Что немного, учитывая тот факт, что каждое изменяемое свойство в любом случае имеет как минимум семь строк кода в классе My. - person Holger; 03.07.2014
comment
Я написал пост на эту же тему. По сути, это длинная текстовая версия этого ответа: Serialize Optional - person Nicolai Parlog; 16.09.2016
comment
Недостатком является то, что вам нужно сделать это для любого bean/pojo, который использует опции. Но все же, хорошая идея. - person GhostCat; 11.12.2017

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

person Eric Hartford    schedule 22.09.2016

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

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

person user207421    schedule 03.07.2014
comment
Если я владею кодом класса, удобнее хранить в поле простой объект. Необязательный класс в таком случае будет ограничен интерфейсом класса (метод get вернет Optional.ofNullable(field)). Но для внутреннего представления невозможно использовать Optional, чтобы явно указать, что значение является необязательным. - person vanarchi; 03.07.2014
comment
Я только что показал, что это возможно. Если вы по какой-то причине считаете иначе, о чем именно ваш вопрос? - person user207421; 03.07.2014
comment
Спасибо за ваш ответ, он добавляет мне возможность. В своем комментарии я хотел показать дальнейшие мысли по этой теме, рассмотреть плюсы и минусы каждого решения. При использовании методов writeObject/readObject у нас есть четкое намерение необязательного в представлении состояния, но реализация сериализации становится более сложной. Если поле интенсивно используется в вычислениях/потоках - удобнее использовать writeObject/readObject. - person vanarchi; 03.07.2014
comment
Комментарии должны быть связаны с ответами, под которыми они появляются. Релевантность ваших размышлений, честно говоря, ускользает от меня. - person user207421; 04.07.2014

Библиотека Vavr.io (ранее Javaslang) также имеет сериализуемый класс Option:

public interface Option<T> extends Value<T>, Serializable { ... }
person Przemek Nowak    schedule 27.08.2018

Если вы хотите поддерживать более согласованный список типов и избегать использования 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 и, таким образом, слишком большой сериализации.

person Dan Gravell    schedule 30.01.2019