Warum java.util.Optional nicht serialisierbar ist, wie das Objekt mit solchen Feldern serialisiert wird

106

Die Enum-Klasse ist serialisierbar, sodass es kein Problem gibt, Objekte mit Enums zu serialisieren. Der andere Fall ist, wenn die Klasse Felder der Klasse java.util.Optional enthält. In diesem Fall wird die folgende Ausnahme ausgelöst: java.io.NotSerializableException: java.util.Optional

Wie gehe ich mit solchen Klassen um, wie serialisiere ich sie? Ist es möglich, solche Objekte an Remote EJB oder über RMI zu senden?

Dies ist das Beispiel:

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
quelle
Wenn Optionalmarkiert als Serializable, was würde dann passieren, wenn get()etwas zurückgegeben würde, das nicht serialisierbar war?
WW.
10
@WW. Sie würden NotSerializableException,natürlich eine bekommen.
Marquis von Lorne
7
@WW. Es ist wie bei Sammlungen. Die meisten Auflistungsklassen sind serialisierbar, aber eine Auflistungsinstanz kann tatsächlich nur serialisiert werden, wenn jedes in der Auflistung enthaltene Objekt auch serialisierbar ist.
Stuart Marks
Meine persönliche Einstellung hier: Es geht nicht darum, die Leute daran zu erinnern, selbst die Serialisierung von Objekten richtig zu testen. Ich bin gerade auf java.io.NotSerializableException (java.util.Optional) selbst gestoßen ;-(
GhostCat

Antworten:

170

Diese Antwort ist eine Antwort auf die Frage im Titel: "Sollte Optional nicht serialisierbar sein?" Die kurze Antwort lautet, dass die Expertengruppe Java Lambda (JSR-335) dies geprüft und abgelehnt hat . Dieser und dieser sowie dieser Hinweis geben an, dass das primäre Entwurfsziel für darin Optionalbesteht, als Rückgabewert von Funktionen verwendet zu werden, wenn möglicherweise kein Rückgabewert vorhanden ist. Die Absicht ist, dass der Anrufer Optionalden aktuellen Wert sofort überprüft und extrahiert, falls vorhanden. Wenn der Wert nicht vorhanden ist, kann der Aufrufer einen Standardwert ersetzen, eine Ausnahme auslösen oder eine andere Richtlinie anwenden. Dies erfolgt normalerweise durch Verketten fließender Methodenaufrufe am Ende einer Stream-Pipeline (oder anderer Methoden), die OptionalWerte zurückgeben.

Es war niemals dafür gedacht Optional, auf andere Weise verwendet zu werden, beispielsweise für optionale Methodenargumente oder als Feld in einem Objekt gespeichert zu werden . Durch die OptionalSerialisierung kann es dauerhaft gespeichert oder über ein Netzwerk übertragen werden. Beides fördert die Verwendung weit über das ursprüngliche Entwurfsziel hinaus.

Normalerweise gibt es bessere Möglichkeiten, die Daten zu organisieren, als sie Optionalin einem Feld zu speichern . Wenn ein Getter (wie die getValueMethode in der Frage) das Ist Optionalaus dem Feld zurückgibt , zwingt er jeden Aufrufer, eine Richtlinie für den Umgang mit einem leeren Wert zu implementieren. Dies führt wahrscheinlich zu einem inkonsistenten Verhalten aller Anrufer. Es ist oft besser, wenn die Codesätze in diesem Feld zum Zeitpunkt der Festlegung eine Richtlinie anwenden.

Manchmal möchten Leute Optionalin Sammlungen setzen, wie List<Optional<X>>oder Map<Key,Optional<Value>>. Auch das ist normalerweise eine schlechte Idee. Oft ist es besser, diese Verwendungen Optionaldurch Null-Objekt- Werte (keine tatsächlichen nullReferenzen) zu ersetzen oder diese Einträge einfach vollständig aus der Sammlung zu streichen.

Stuart Marks
quelle
26
Gut gemacht, Stuart. Ich muss sagen, es ist eine außergewöhnliche Kette, wenn man argumentiert, und es ist eine außergewöhnliche Sache, eine Klasse zu entwerfen, die nicht als Instanzmitgliedstyp verwendet werden soll. Besonders wenn das nicht im Klassenvertrag im Javadoc steht. Vielleicht hätten sie eine Anmerkung anstelle einer Klasse entwerfen sollen.
Marquis von Lorne
1
Dies ist ein ausgezeichneter Blog-Beitrag über die Gründe für Sutarts Antwort: blog.joda.org/2014/11/optional-in-java-se-8.html
Wesley Hartford
2
Gut, dass ich keine serialisierbaren Felder verwenden muss. Dies wäre sonst eine Katastrophe
Kurru
48
Interessante Antwort, für mich war die Wahl des Designs völlig inakzeptabel und falsch. Sie sagen: "Normalerweise gibt es bessere Möglichkeiten, die Daten zu organisieren, als eine Option in einem Feld zu speichern." Sicher, vielleicht, warum nicht, aber das sollte die Wahl des Designers sein, nicht die der Sprache. Es ist ein weiterer dieser Fälle, in denen ich Scala-Optionen in Java sehr vermisse (Scala-Optionen sind serialisierbar und folgen den Richtlinien von Monad)
Guillaume,
36
"Das primäre Entwurfsziel für Optional ist die Verwendung als Rückgabewert von Funktionen, wenn möglicherweise kein Rückgabewert vorhanden ist." Nun, es scheint, dass Sie sie nicht für Rückgabewerte in einem Remote-EJB verwenden können. Großartig ...
Thilo
15

Viel Serialization verwandte Probleme können gelöst werden, indem Sie das persistente serialisierte Formular von der tatsächlichen Laufzeitimplementierung entkoppeln, mit der Sie arbeiten.

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

Die Klasse Optional implementiert ein Verhalten, das es ermöglicht, guten Code zu schreiben, wenn möglicherweise fehlende Werte verarbeitet werden (im Vergleich zur Verwendung von null). Eine dauerhafte Darstellung Ihrer Daten bietet jedoch keinen Vorteil. Es würde nur Ihre serialisierten Daten größer machen ...

Die obige Skizze mag kompliziert aussehen, aber das liegt daran, dass sie das Muster nur mit einer Eigenschaft demonstriert. Je mehr Eigenschaften Ihre Klasse hat, desto mehr sollte ihre Einfachheit offenbart werden.

Und nicht zu vergessen, die Möglichkeit, die Implementierung Mykomplett zu ändern, ohne die persistente Form anpassen zu müssen…

Holger
quelle
2
+1, aber mit mehr Feldern gibt es mehr Boilerplate, um sie zu kopieren.
Marko Topolnik
1
@ Marko Topolnik: höchstens eine einzelne Zeile pro Eigenschaft und Richtung. Die Bereitstellung eines geeigneten Konstruktors für class My, den Sie normalerweise auch für andere Zwecke verwenden, readResolvekann jedoch eine einzeilige Implementierung sein, wodurch die Boilerplate auf eine einzige Zeile pro Eigenschaft reduziert wird. Was angesichts der Tatsache, dass jede veränderbare Eigenschaft Myohnehin mindestens sieben Codezeilen in der Klasse hat, nicht viel ist.
Holger
Ich habe einen Beitrag zum gleichen Thema geschrieben. Es ist im Wesentlichen die Langtextversion
Nicolai
Nachteil ist: Sie müssen dies für jede Bohne / Pojo tun, die Optionals verwendet. Aber trotzdem schöne Idee.
GhostCat
4

Es ist eine merkwürdige Auslassung.

Sie müssten das Feld als markieren transientund Ihre eigene benutzerdefinierte writeObject()Methode angeben , mit der das get()Ergebnis selbst geschrieben wurde, sowie eine readObject()Methode, mit der Optionaldas Ergebnis wiederhergestellt wird, indem das Ergebnis aus dem Stream gelesen wird. Nicht zu vergessen , rufen defaultWriteObject()und defaultReadObject()jeweils.

Marquis von Lorne
quelle
Wenn ich den Code einer Klasse besitze, ist es bequemer, nur ein Objekt in einem Feld zu speichern. In diesem Fall wäre die optionale Klasse für die Schnittstelle der Klasse eingeschränkt (die get-Methode würde Optional.ofNullable (Feld) zurückgeben). Für die interne Darstellung ist es jedoch nicht möglich, Optional zu verwenden, um eindeutig darauf hinzuweisen, dass der Wert optional ist.
Vanarchi
Ich habe gerade gezeigt , dass es ist möglich. Wenn Sie aus irgendeinem Grund anders denken, worum geht es in Ihrer Frage genau?
Marquis von Lorne
Vielen Dank für Ihre Antwort, es fügt eine Option für mich hinzu. In meinem Kommentar wollte ich weitere Gedanken zu diesem Thema zeigen, Pro und Contra jeder Lösung betrachten. Bei der Verwendung von writeObject / readObject-Methoden haben wir die klare Absicht Optional in der Zustandsdarstellung, aber die Implementierung der Serialisierung wird komplizierter. Wenn das Feld in Berechnungen / Streams intensiv verwendet wird, ist es bequemer, writeObject / readObject zu verwenden.
Vanarchi
Kommentare sollten für die Antworten relevant sein, unter denen sie erscheinen. Die Relevanz Ihrer Überlegungen entgeht mir offen.
Marquis von Lorne
3

Die Vavr.io-Bibliothek (ehemals Javaslang) hat auch die OptionKlasse, die serialisierbar ist:

public interface Option<T> extends Value<T>, Serializable { ... }
Przemek Nowak
quelle
0

Wenn Sie eine konsistentere Typliste pflegen und die Verwendung von Null vermeiden möchten, gibt es eine verrückte Alternative.

Sie können den Wert über einen Schnittpunkt von Typen speichern . In Verbindung mit einem Lambda ermöglicht dies Folgendes:

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

Wenn die tempVariable getrennt ist, wird vermieden, dass der Eigentümer des valueMitglieds geschlossen wird und zu viel serialisiert wird.

Dan Gravell
quelle