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

Класс Enum является Serializable, поэтому нет проблем с сериализацией объекта с перечислениями. В другом случае класс имеет поля java.util.Optional class. В этом случае бросается следующее исключение: 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);
        }
    }
}

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

Если вы хотите, чтобы сериализуемый факультативный, рассмотрите вместо этого guava optional, который можно сериализовать.

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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