Dlaczego java.util.Optional nie można serializować, jak serializować obiekt za pomocą takich pól

107

Klasa Enum jest Serializable, więc nie ma problemu z serializacją obiektu za pomocą wyliczeń. W drugim przypadku klasa ma pola klasy java.util.Optional. W takim przypadku zgłaszany jest następujący wyjątek: java.io.NotSerializableException: java.util.Optional

Jak sobie radzić z takimi klasami, jak je serializować? Czy można wysłać takie obiekty do zdalnego EJB lub przez RMI?

Oto przykład:

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);
        }
    }
}
vanarchi
źródło
Jeśli Optionalzostał oznaczony jako Serializable, co by się stało, gdyby get()zwrócił coś, czego nie można serializować?
WW.
10
@W W. Oczywiście dostaniesz NotSerializableException,.
Markiz Lorne,
7
@W W. To jak kolekcje. Większość klas kolekcji można serializować, ale wystąpienie kolekcji może być serializowane tylko wtedy, gdy każdy obiekt zawarty w kolekcji jest również możliwy do serializacji.
Stuart Marks
Mój osobisty pogląd: nie chodzi o przypominanie ludziom o prawidłowym testowaniu jednostkowym, nawet serializacji obiektów. Właśnie wpadłem na java.io.NotSerializableException (java.util.Optional) siebie ;-(
GhostCat,

Odpowiedzi:

171

Ta odpowiedź jest odpowiedzią na pytanie w tytule „Czy opcjonalny nie powinien być serializowany?” Krótka odpowiedź jest taka, że ​​grupa ekspertów Java Lambda (JSR-335) rozważyła to i odrzuciła . Ta uwaga, ta i ta wskazują, że głównym celem projektu Optionaljest użycie jako wartości zwracanej funkcji, gdy wartość zwracana może być nieobecna. Celem jest, aby wywołujący natychmiast sprawdził Optionali wyodrębnił rzeczywistą wartość, jeśli jest obecna. Jeśli wartość jest nieobecna, obiekt wywołujący może zastąpić wartość domyślną, zgłosić wyjątek lub zastosować inne zasady. Odbywa się to zwykle przez połączenie wywołań metod płynnych na końcu potoku strumienia (lub innych metod), które zwracają Optionalwartości.

Nigdy nie było przeznaczone Optionaldo używania go w inny sposób, na przykład do opcjonalnych argumentów metod lub do przechowywania jako pole w obiekcie . A co za tym idzie, umożliwienie Optionalserializacji umożliwiłoby trwałe przechowywanie lub przesyłanie przez sieć, co zachęca do zastosowań daleko wykraczających poza pierwotny cel projektu.

Zwykle istnieją lepsze sposoby organizowania danych niż przechowywanie Optionalw polu. Jeśli getValuemetoda pobierająca (taka jak metoda w pytaniu) zwraca wartość rzeczywistą Optionalz pola, to zmusza każdego wywołującego do zaimplementowania pewnych zasad postępowania z wartością pustą. Prawdopodobnie doprowadzi to do niespójnego zachowania wszystkich rozmówców. Często lepiej jest mieć dowolny kod ustawiany przez to pole, który stosuje pewne zasady w momencie ich ustawiania.

Czasami ludzie chcą umieścić je Optionalw kolekcjach, takich jak List<Optional<X>>lub Map<Key,Optional<Value>>. To też jest zazwyczaj zły pomysł. Jest to często lepiej zastąpić te zwyczaje Optionalz Null-Object wartości (a nie rzeczywiste nullreferencje), lub po prostu pominąć te wpisy z kolekcji w całości.

Stuart Marks
źródło
26
Dobra robota, Stuart. Muszę powiedzieć, że jest to niezwykły łańcuch, jeśli rozumowanie, i niezwykłą rzeczą jest zaprojektowanie klasy, która nie jest przeznaczona do użycia jako typ elementu członkowskiego instancji. Zwłaszcza, gdy nie jest to określone w umowie klasowej w Javadoc. Może powinni byli zaprojektować adnotację zamiast klasy.
Markiz Lorne,
1
To jest doskonały post na blogu na temat uzasadnienia odpowiedzi Sutarta
Wesley Hartford
2
Dobrze, że nie muszę używać pól serializowalnych. W przeciwnym razie byłaby to katastrofa
Kurru
48
Ciekawa odpowiedź, dla mnie ten wybór projektu był całkowicie niedopuszczalny i chybiony. Mówisz: „Zwykle istnieją lepsze sposoby organizowania danych niż przechowywanie danych opcjonalnych w polu”. Oczywiście, być może, dlaczego nie, ale to powinien być wybór projektanta, a nie języka. To kolejny z tych przypadków, w których bardzo tęsknię za opcjami Scala w Javie (
Guillaume.
37
„głównym celem projektu dla Optional jest użycie jako wartości zwracanej funkcji, gdy wartość zwracana może być nieobecna”. Wygląda na to, że nie można ich używać do zwracania wartości w zdalnym komponencie EJB. Świetnie ...
Thilo
15

Wiele Serializationpowiązanych problemów można rozwiązać, oddzielając trwały formularz serializowany od rzeczywistej implementacji środowiska uruchomieniowego, na którym działasz.

/** 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;
    }
}

Klasa Optionalimplementuje zachowanie, które pozwala napisać dobry kod w przypadku potencjalnie nieobecnych wartości (w porównaniu do użycia null). Ale to nie dodaje żadnych korzyści do trwałej reprezentacji twoich danych. To po prostu zwiększyłoby twoje zserializowane dane ...

Powyższy szkic może wyglądać na skomplikowany, ale to dlatego, że przedstawia wzór tylko z jedną właściwością. Im więcej właściwości ma twoja klasa, tym bardziej powinna być ujawniona jej prostota.

I nie zapominajmy, że możliwość zmiany wykonania Mycałkowicie bez konieczności dostosowywania trwałej formy…

Holger
źródło
2
+1, ale przy większej liczbie pól będzie więcej schematów do ich kopiowania.
Marko Topolnik
1
@Marko Topolnik: najwyżej jeden wiersz na właściwość i kierunek. Jednak dostarczenie odpowiedniego konstruktora class My, który zwykle jest przydatny również w innych zastosowaniach, readResolvemoże być implementacją jednowierszową, zmniejszając w ten sposób standardowy schemat do jednego wiersza na właściwość. A to niewiele, biorąc pod uwagę fakt, że każda zmienna właściwość ma i tak co najmniej siedem wierszy kodu w klasie My.
Holger,
Napisałem post na ten sam temat. Jest to zasadniczo długa wersja tekstowa tej odpowiedzi: Serialize Optional
Nicolai
Wadą jest: musisz to zrobić dla każdego ziarna / pojo, które używa opcji. Ale nadal niezły pomysł.
GhostCat
12

Jeśli chcesz serializowalny opcjonalny, rozważ zamiast tego użycie opcjonalnego guawy, który można serializować.

Eric Hartford
źródło
4

To dziwne zaniedbanie.

Musisz oznaczyć pole jako transienti podać własną niestandardową writeObject()metodę, która zapisała get()sam wynik, oraz readObject()metodę, która przywróciła wynik Optional, odczytując ten wynik ze strumienia. Nie zapomnij zadzwonić defaultWriteObject()i defaultReadObject()odpowiednio.

Markiz Lorne
źródło
Jeśli jestem właścicielem kodu klasy, wygodniej jest przechowywać zwykły obiekt w polu. Klasa opcjonalna w takim przypadku byłaby ograniczona dla interfejsu klasy (metoda get zwróciłaby parametr Optional.ofNullable (pole)). Ale w przypadku reprezentacji wewnętrznej nie można użyć opcjonalnego, aby jasno określić, że wartość jest opcjonalna.
vanarchi
Właśnie pokazałem, że jest to możliwe. Jeśli z jakiegoś powodu myślisz inaczej, o co właściwie chodzi?
Markiz Lorne,
Dziękuję za odpowiedź, dodaje opcję dla mnie. W swoim komentarzu chciałem przedstawić dalsze przemyślenia na ten temat, rozważyć Pro i contra każdego rozwiązania. W metodach writeObject / readObject mamy wyraźną intencję Optional w reprezentacji stanu, ale implementacja serializacji staje się bardziej skomplikowana. Jeśli pole jest intensywnie wykorzystywane w obliczeniach / strumieniach - wygodniej jest używać writeObject / readObject.
vanarchi
Komentarze powinny odnosić się do odpowiedzi, pod którymi się pojawiają. Szczerze mówiąc, umyka mi znaczenie twoich rozważań.
Markiz Lorne,
3

Biblioteka Vavr.io (dawniej Javaslang) ma również Optionklasę, którą można serializować:

public interface Option<T> extends Value<T>, Serializable { ... }
Przemek Nowak
źródło
0

Jeśli chcesz zachować bardziej spójną listę typów i unikać używania null, jest jedna dziwna alternatywa.

Możesz zapisać wartość, używając przecięcia typów . W połączeniu z lambdą pozwala to na coś takiego:

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();

Oddzielenie tempzmiennej pozwala uniknąć zamykania właściciela elementu valueczłonkowskiego, a tym samym zbyt dużej serializacji.

Dan Gravell
źródło