web-dev-qa-db-fra.com

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

La classe Enum est sérialisable, il n'y a donc aucun problème à sérialiser un objet avec des enums. L'autre cas est 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 traiter avec de telles classes, comment les sérialiser? Est-il possible d'envoyer de tels objets à des EJB distants 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);
        }
    }
}
90
vanarchi

Cette réponse est en réponse à la question dans le titre, "Ne devrait pas être facultatif être sérialisable?" La réponse courte est que le groupe d'experts Java Lambda (JSR-335) l'a examiné et l'a rejeté . Cette note et celle-ci et celle-ci indiquent que l'objectif de conception principal de Optional doit ê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 la Optional et extrait la valeur réelle si elle est présente. Si la valeur est absente, l'appelant peut substituer une valeur par défaut, émettre une exception ou appliquer une autre stratégie. Cela se fait généralement en enchaînant des appels de méthode fluents à la fin d'un pipeline de flux (ou d'autres méthodes) qui renvoient des valeurs Optional.

Il n’a jamais été prévu que Optional soit utilisé d’une autre manière, par exemple pour les arguments de méthode facultatifs ou soit stockés sous la forme d’un champ dans un objet . Et par extension, rendre Optional sérialisable lui permettrait d'être stocké de manière persistante ou transmis sur un réseau, ce qui encouragerait des utilisations bien au-delà de son objectif de conception d'origine.

Il existe généralement de meilleurs moyens d'organiser les données que de stocker une Optional dans un champ. Si un getter (tel que la méthode getValue dans la question) renvoie la valeur réelle Optional à partir du champ, il oblige chaque appelant à mettre en œuvre une stratégie permettant de 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 certaines règles au moment où ils sont définis.

Parfois, les gens veulent mettre Optional dans des collections, comme List<Optional<X>> ou Map<Key,Optional<Value>>. Cela aussi est généralement une mauvaise idée. Il est souvent préférable de remplacer ces utilisations de Optional par Null-Object values ​​(pas de références null actuelles), ou tout simplement d’omettre entièrement ces entrées de la collection.

134
Stuart Marks

Un grand nombre de problèmes liés à Serialization peuvent être résolus en découplant le formulaire sérialisé persistant de l'implémentation d'exécution réelle sur laquelle vous opérez.

/** 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 Optional implémente behavior qui permet d'écrire un 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 rendrait simplement vos données sérialisées plus grandes…

L’esquisse ci-dessus peut paraître compliquée, mais c’est parce qu’elle montre le motif 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 changer complètement l'implémentation de My sans qu'il soit nécessaire d'adapter la forme persistante…

12
Holger

Si vous souhaitez une option sérialisable optionnelle, utilisez plutôt optionnel de guava qui est sérialisable.

7
Eric Hartford

C'est une omission curieuse.

Vous devez marquer le champ comme étant transient et fournir votre propre méthode writeObject() personnalisée qui a écrit le résultat get() lui-même et une méthode readObject() qui a restauré la Optional en lisant ce résultat dans le flux. Sans oublier d'appeler defaultWriteObject() et defaultReadObject() respectivement.

3
user207421

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

Vous pouvez stocker la valeur en utilisant une intersection de types . Couplé avec 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();

Le fait de séparer la variable temp évite de fermer sur le propriétaire du membre value et donc de sérialiser trop.

0
Dan Gravell

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

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