Perché java.util.Optional non è serializzabile, come serializzare l'oggetto con tali campi


107

La classe Enum è Serializable quindi non ci sono problemi a serializzare l'oggetto con enumerazioni. L'altro caso è dove la classe ha i campi della classe java.util.Optional. In questo caso viene generata la seguente eccezione: java.io.NotSerializableException: java.util.Optional

Come gestire tali classi, come serializzarle? È possibile inviare tali oggetti a EJB remoto o tramite RMI?

Questo è l'esempio:

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

Se Optionalfosse contrassegnato come Serializable, cosa succederebbe se get()restituisse qualcosa che non era serializzabile?
WW.

10
@WW. Avresti NotSerializableException,ovviamente.
Marchese di Lorne

7
@WW. È proprio come le collezioni. La maggior parte delle classi di raccolta sono serializzabili, ma un'istanza di raccolta può effettivamente essere serializzata solo se anche ogni oggetto contenuto nella raccolta è serializzabile.
Stuart Marks

La mia opinione personale qui: non è per ricordare alle persone di eseguire correttamente i test unitari anche la serializzazione degli oggetti. Mi sono appena imbattuto in java.io.NotSerializableException (java.util.Optional) io stesso
;-(

Risposte:


171

Questa risposta è in risposta alla domanda nel titolo, "Non dovrebbe essere facoltativo essere serializzabile?" La risposta breve è che il gruppo di esperti Java Lambda (JSR-335) lo ha considerato e rifiutato . Quella nota, e questa e questa indicano che l'obiettivo di progettazione principale per Optionaldeve essere utilizzato come valore di ritorno delle funzioni quando un valore di ritorno potrebbe essere assente. L'intento è che il chiamante controlli immediatamente Optionaled estragga il valore effettivo se presente. Se il valore è assente, il chiamante può sostituire un valore predefinito, generare un'eccezione o applicare altri criteri. Questa operazione viene in genere eseguita concatenando chiamate di metodi fluenti alla fine di una pipeline di flusso (o altri metodi) che restituiscono Optionalvalori.

Non è mai stato concepito per Optionalessere utilizzato in altri modi, ad esempio per argomenti di metodo opzionali o per essere memorizzato come campo in un oggetto . E per estensione, renderlo Optionalserializzabile consentirebbe di archiviarlo in modo persistente o trasmetterlo attraverso una rete, il che incoraggia entrambi gli usi ben oltre il suo obiettivo di progettazione originale.

Di solito ci sono modi migliori per organizzare i dati che memorizzare un Optionalin un campo. Se un getter (come il getValuemetodo nella domanda) restituisce l'effettivo Optionaldal campo, costringe ogni chiamante a implementare una politica per gestire un valore vuoto. Questo probabilmente porterà a comportamenti incoerenti tra i chiamanti. Spesso è meglio che qualsiasi set di codice in quel campo applichi una politica nel momento in cui è impostato.

A volte le persone vogliono mettere Optionalin raccolte, come List<Optional<X>>o Map<Key,Optional<Value>>. Anche questa di solito è una cattiva idea. Spesso è meglio sostituire questi usi di Optionalcon valori Null-Object (non nullriferimenti effettivi ) o semplicemente omettere completamente queste voci dalla raccolta.


26
Ben fatto Stuart. Devo dire che è una catena straordinaria se si ragiona, ed è una cosa straordinaria da fare per progettare una classe che non deve essere utilizzata come tipo di membro di istanza. Soprattutto quando questo non è indicato nel contratto di classe nel Javadoc. Forse avrebbero dovuto progettare un'annotazione invece di una classe.
Marchese di Lorne

1
Questo è un eccellente post sul blog sulla logica alla base della risposta di Sutart
Wesley Hartford

2
Meno male che non ho bisogno di usare campi serializzabili. Altrimenti sarebbe un disastro
Kurru

48
Risposta interessante, per me la scelta del design era del tutto inaccettabile e fuorviante. Dici "Di solito ci sono modi migliori per organizzare i dati che memorizzare un Opzionale in un campo", certo, forse, perché no, ma dovrebbe essere una scelta del designer, non della lingua. È un altro di questi casi in cui mi mancano profondamente gli optional Scala in Java (gli optional Scala sono serializzabili e seguono le linee guida di Monad)
Guillaume

37
"l'obiettivo di progettazione principale per Facoltativo deve essere utilizzato come valore restituito dalle funzioni quando un valore restituito potrebbe essere assente.". Bene, sembra che non puoi usarli per i valori di ritorno in un EJB remoto. Fantastico ...
Thilo

15

Molti Serializationproblemi correlati possono essere risolti disaccoppiando il modulo serializzato persistente dall'effettiva implementazione del runtime su cui si opera.

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

La classe Optionalimplementa un comportamento che permette di scrivere un buon codice quando si tratta di valori eventualmente assenti (rispetto all'uso di null). Ma non aggiunge alcun vantaggio a una rappresentazione persistente dei dati. Renderebbe i tuoi dati serializzati più grandi ...

Lo schizzo sopra potrebbe sembrare complicato, ma è perché mostra il modello con una sola proprietà. Più proprietà ha la tua classe, più dovrebbe essere rivelata la sua semplicità.

E non dimenticare, la possibilità di cambiare Mycompletamente l'implementazione senza alcuna necessità di adattare la forma persistente ...


2
+1, ma con più campi ci saranno più boilerplate per copiarli.
Marko Topolnik

1
@Marko Topolnik: al massimo una sola riga per proprietà e direzione. Tuttavia, fornendo un costruttore appropriato per class My, cosa che di solito fai perché è utile anche per altri usi, readResolvepotrebbe essere un'implementazione a riga singola riducendo così il boilerplate a una singola riga per proprietà. Il che non è molto dato che ogni proprietà modificabile ha comunque almeno sette righe di codice in classe My.
Holger

Ho scritto un post sullo stesso argomento. È essenzialmente la versione di testo lungo di questa risposta: Serializzazione opzionale
Nicolai

Lo svantaggio è: devi farlo per qualsiasi bean / pojo che utilizza gli optional. Ma comunque, bella idea.
GhostCat


4

È una curiosa omissione.

Dovresti contrassegnare il campo come transiente fornire il tuo writeObject()metodo personalizzato che ha scritto il get()risultato stesso e un readObject()metodo che ripristina il Optionalleggendo quel risultato dallo stream. Senza dimenticare di chiamare defaultWriteObject()e defaultReadObject()rispettivamente.


Se possiedo il codice di una classe è più conveniente memorizzare un semplice oggetto in un campo. La classe opzionale in questo caso sarebbe limitata per l'interfaccia della classe (il metodo get restituirebbe Optional.ofNullable (campo)). Ma per la rappresentazione interna non è possibile utilizzare Opzionale per intendere chiaramente che il valore è opzionale.
vanarchi

Ho appena dimostrato che è possibile. Se per qualche motivo la pensi diversamente, qual è esattamente la tua domanda?
Marchese di Lorne

Grazie per la tua risposta, aggiunge un'opzione per me. Nel mio commento ho voluto mostrare ulteriori riflessioni su questo argomento, considerare Pro e contro di ogni soluzione. Nell'uso dei metodi writeObject / readObject abbiamo un chiaro intento di Optional nella rappresentazione dello stato, ma l'implementazione della serializzazione diventa più complicata. Se il campo viene utilizzato in modo intensivo nel calcolo / flussi, è più conveniente utilizzare writeObject / readObject.
vanarchi

I commenti dovrebbero essere pertinenti alle risposte sotto cui appaiono. La rilevanza delle tue riflessioni mi sfugge francamente.
Marchese di Lorne

3

La libreria Vavr.io (ex Javaslang) ha anche la Optionclasse che è serializzabile:

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

0

Se vuoi mantenere un elenco di tipi più coerente ed evitare di usare null, c'è un'alternativa stravagante.

È possibile memorizzare il valore utilizzando un'intersezione di tipi . Accoppiato con un lambda, questo consente qualcosa come:

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

Avere la tempvariabile separata evita di chiudersi sul proprietario del valuemembro e quindi di serializzare troppo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.