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);
}
}
}
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.
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…
Si vous souhaitez une option sérialisable optionnelle, utilisez plutôt optionnel de guava qui est sérialisable.
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.
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.
La bibliothèque Vavr.io (anciennement Javaslang) a également la classe Option
qui est sérialisable:
public interface Option<T> extends Value<T>, Serializable { ... }