Pourquoi java.util.Optional n'est pas sérialisable, comment sérialiser l'objet avec de tels champs


107

La classe Enum est Serializable donc il n'y a aucun problème pour sérialiser un objet avec des enums. L'autre cas est celui où la classe a des champs de la classe java.util.Optional. Dans ce cas, l'exception suivante est levée: java.io.NotSerializableException: java.util.Optional

Comment gérer de telles classes, comment les sérialiser? Est-il possible d'envoyer de tels objets à Remote EJB ou via RMI?

Voici l'exemple:

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

Si Optionalétait marqué comme Serializable, que se passerait-il si get()retournait quelque chose qui n'était pas sérialisable?
WW.

10
@WW. Vous obtiendrez un NotSerializableException,bien sûr.
Marquis of Lorne

7
@WW. C'est comme des collections. La plupart des classes de collection sont sérialisables, mais une instance de collection ne peut en fait être sérialisée que si chaque objet contenu dans la collection est également sérialisable.
Stuart marque le

Mon point de vue personnel ici: il ne s'agit pas de rappeler aux gens de tester correctement même la sérialisation des objets. Je viens de rencontrer java.io.NotSerializableException (java.util.Optional) moi-même ;-(
GhostCat

Réponses:


171

Cette réponse est en réponse à la question du titre, "L'option facultative ne devrait-elle pas être sérialisable?" La réponse courte est que le groupe d'experts Java Lambda (JSR-335) l'a considérée et rejetée . Cette note, et celle-ci et celle-ci indiquent que l'objectif principal de conception pour Optionalest d'être utilisé comme valeur de retour des fonctions lorsqu'une valeur de retour peut être absente. L'intention est que l'appelant vérifie immédiatement le Optionalet extrait la valeur réelle si elle est présente. Si la valeur est absente, l'appelant peut remplacer une valeur par défaut, lever une exception ou appliquer une autre stratégie. Cela se fait généralement en chaînant les appels de méthode fluent à la fin d'un pipeline de flux (ou d'autres méthodes) qui renvoient des Optionalvaleurs.

Il n'a jamais été conçu pour Optionalêtre utilisé d'une autre manière, comme pour des arguments de méthode facultatifs ou pour être stocké sous forme de champ dans un objet . Et par extension, rendre Optionalsérialisable lui permettrait d'être stocké de manière persistante ou transmis sur un réseau, ce qui encourage les utilisations bien au-delà de son objectif de conception d'origine.

Il existe généralement de meilleures façons d'organiser les données que de les stocker Optionaldans un champ. Si un getter (tel que la getValueméthode dans la question) renvoie le réel Optionaldu champ, il force chaque appelant à implémenter une politique pour traiter une valeur vide. Cela entraînera probablement un comportement incohérent entre les appelants. Il est souvent préférable que les jeux de codes de ce champ appliquent une politique au moment où ils sont définis.

Parfois, les gens veulent mettre Optionaldans des collections, comme List<Optional<X>>ou Map<Key,Optional<Value>>. Ceci aussi est généralement une mauvaise idée. Il est souvent préférable de remplacer ces utilisations de Optionalpar des valeurs Null-Object (pas de nullréférences réelles ), ou simplement d'omettre complètement ces entrées de la collection.


26
Bravo Stuart. Je dois dire que c'est une chaîne extraordinaire pour le raisonnement, et c'est une chose extraordinaire pour concevoir une classe qui n'est pas destinée à être utilisée comme type de membre d'instance. Surtout quand cela n'est pas indiqué dans le contrat de classe dans le Javadoc. Peut-être qu'ils auraient dû concevoir une annotation au lieu d'une classe.
Marquis of Lorne

1
Ceci est un excellent article de blog sur la justification de la réponse de Sutart
Wesley Hartford

2
Heureusement que je n'ai pas besoin d'utiliser des champs sérialisables. Ce serait un désastre autrement
Kurru

48
Réponse intéressante, pour moi, ce choix de conception était complètement inacceptable et malavisé. Vous dites "Habituellement, il y a de meilleures façons d'organiser les données que de stocker un Optionnel dans un champ", bien sûr, peut-être, pourquoi pas, mais cela devrait être le choix du concepteur, pas celui du langage. C'est un autre de ces cas où les options Scala en Java me manquent profondément (les options Scala sont sérialisables et elles suivent les directives de Monad)
Guillaume

37
"le principal objectif de conception pour Optional est d'être utilisé comme valeur de retour des fonctions lorsqu'une valeur de retour peut être absente." Eh bien, il semble que vous ne pouvez pas les utiliser pour les valeurs de retour dans un EJB distant. Great ...
Thilo

15

Un grand nombre de Serializationproblèmes connexes peuvent être résolus en découplant le formulaire sérialisé persistant de l'implémentation d'exécution réelle sur laquelle vous travaillez.

/** 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 Optionalimplémente un comportement qui permet d'écrire du bon code lorsqu'il s'agit de valeurs éventuellement absentes (par rapport à l'utilisation de null). Mais cela n'ajoute aucun avantage à une représentation persistante de vos données. Cela ne ferait qu'agrandir vos données sérialisées…

L'esquisse ci-dessus peut sembler compliquée, mais c'est parce qu'elle montre le modèle avec une seule propriété. Plus votre classe a de propriétés, plus sa simplicité doit être révélée.

Et ne pas oublier, la possibilité de modifier Mycomplètement l'implémentation de sans avoir besoin d'adapter la forme persistante…


2
+1, mais avec plus de champs, il y aura plus de passe-partout pour les copier.
Marko Topolnik

1
@Marko Topolnik: au maximum, une seule ligne par propriété et direction. Cependant, en fournissant un constructeur approprié pour class My, ce que vous faites généralement car il est également pratique pour d'autres utilisations, readResolvepourrait être une implémentation sur une seule ligne, réduisant ainsi le passe-partout à une seule ligne par propriété. Ce qui n'est pas grand-chose étant donné que chaque propriété mutable a de toute façon au moins sept lignes de code dans la classe My.
Holger

J'ai écrit un article sur le même sujet. C'est essentiellement la version texte longue de cette réponse: Serialize Optional
Nicolai

L'inconvénient est que vous devez le faire pour tout bean / pojo qui utilise des options. Mais quand même, bonne idée.
GhostCat


4

C'est une curieuse omission.

Vous devrez marquer le champ comme transientet fournir votre propre writeObject()méthode personnalisée qui a écrit le get()résultat lui-même, et une readObject()méthode qui a restauré le Optionalen lisant ce résultat à partir du flux. Sans oublier d'appeler defaultWriteObject()et defaultReadObject()respectivement.


Si je possède le code d'une classe, il est plus pratique de stocker un simple objet dans un champ. La classe facultative dans un tel cas serait restreinte pour l'interface de la classe (la méthode get renverrait Optional.ofNullable (field)). Mais pour la représentation interne, il n'est pas possible d'utiliser Optionnel pour indiquer clairement que la valeur est facultative.
vanarchi

Je viens de vous montrer qu'il est possible. Si vous pensez le contraire pour une raison quelconque, sur quoi porte exactement votre question?
Marquis of Lorne

Merci pour votre réponse, cela ajoute une option pour moi. Dans mon commentaire, je voulais montrer d'autres réflexions sur ce sujet, considérer les avantages et les inconvénients de chaque solution. Dans l'utilisation des méthodes writeObject / readObject, nous avons une intention claire d'Optionnel dans la représentation d'état, mais l'implémentation de la sérialisation devient plus compliquée. Si le champ est utilisé de manière intensive dans le calcul / les flux, il est plus pratique d'utiliser writeObject / readObject.
vanarchi

Les commentaires doivent être pertinents par rapport aux réponses sous lesquelles ils apparaissent. La pertinence de vos réflexions m'échappe franchement.
Marquis of Lorne

3

La bibliothèque Vavr.io (anciennement Javaslang) a également la Optionclasse qui est sérialisable:

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

0

Si vous souhaitez conserver une liste de types plus cohérente et éviter d'utiliser null, il existe une alternative bizarre.

Vous pouvez stocker la valeur à l'aide d'une intersection de types . Couplé à un lambda, cela permet quelque chose comme:

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

La tempséparation de la variable évite de fermer le propriétaire du valuemembre et donc de trop sérialiser.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.