Por qué java.util.Optional no es serializable, cómo serializar el objeto con tales campos

107

La clase Enum es serializable, por lo que no hay ningún problema para serializar objetos con enumeraciones. El otro caso es donde la clase tiene campos de clase java.util.Optional. En este caso, se lanza la siguiente excepción: java.io.NotSerializableException: java.util.Optional

¿Cómo lidiar con tales clases, cómo serializarlas? ¿Es posible enviar tales objetos a Remote EJB oa través de RMI?

Este es el ejemplo:

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
fuente
Si Optionalse marcó como Serializable, ¿qué pasaría si se get()devolviera algo que no se puede serializar?
WW.
10
@WW. Obtendría un NotSerializableException,por supuesto.
Marqués de Lorne
7
@WW. Es como colecciones. La mayoría de las clases de colección son serializables, pero una instancia de colección solo se puede serializar realmente si todos los objetos contenidos en la colección también son serializables.
Stuart Marks
Mi opinión personal aquí: no es para recordarle a las personas que prueben correctamente las unidades, incluso la serialización de objetos. Me encontré con java.io.NotSerializableException (java.util.Optional) yo mismo ;-(
GhostCat

Respuestas:

171

Esta respuesta es en respuesta a la pregunta en el título, "¿No debería ser opcional serializable?" La respuesta corta es que el grupo de expertos Java Lambda (JSR-335) lo consideró y lo rechazó . Esa nota, y esta y esta, indican que el objetivo principal del diseño Optionales que se utilice como valor de retorno de funciones cuando un valor de retorno puede estar ausente. La intención es que la persona que llama verifique inmediatamente Optionaly extraiga el valor real si está presente. Si el valor está ausente, la persona que llama puede sustituir un valor predeterminado, lanzar una excepción o aplicar alguna otra política. Por lo general, esto se hace encadenando llamadas de método fluidas al final de una canalización de flujo (u otros métodos) que devuelven Optionalvalores.

Nunca fue pensado para Optionalser usado de otras formas, como para argumentos de métodos opcionales o para ser almacenado como un campo en un objeto . Y, por extensión, hacer Optionalserializable permitiría almacenarlo de forma persistente o transmitirlo a través de una red, lo que fomenta usos mucho más allá de su objetivo de diseño original.

Por lo general, hay mejores formas de organizar los datos que almacenarlos Optionalen un campo. Si un captador (como el getValuemétodo de la pregunta) devuelve el valor real Optionaldel campo, obliga a cada llamador a implementar alguna política para tratar con un valor vacío. Esto probablemente dará lugar a un comportamiento inconsistente entre las personas que llaman. A menudo es mejor que cualquier conjunto de códigos de ese campo aplique alguna política en el momento en que se establece.

A veces la gente quiere poner Optionalen colecciones, como List<Optional<X>>o Map<Key,Optional<Value>>. Esto también suele ser una mala idea. A menudo es mejor reemplazar estos usos de Optionalcon valores de objeto nulo (no nullreferencias reales ), o simplemente omitir estas entradas de la colección por completo.

Stuart Marks
fuente
26
Bien hecho Stuart. Debo decir que es una cadena extraordinaria si se razona, y es extraordinario diseñar una clase que no está destinada a ser utilizada como un tipo de miembro de instancia. Especialmente cuando eso no se establece en el contrato de clase en el Javadoc. Quizás deberían haber diseñado una anotación en lugar de una clase.
Marqués de Lorne
1
Esta es una excelente publicación de blog sobre el fundamento detrás de la respuesta de Sutart
Wesley Hartford
2
Menos mal que no necesito utilizar campos serializables. De lo contrario
Kurru
48
Interesante respuesta, para mí esa elección de diseño fue completamente inaceptable y equivocada. Dices "Por lo general, hay mejores formas de organizar los datos que almacenar un Opcional en un campo", claro, tal vez, por qué no, pero esa debe ser la elección del diseñador, no del idioma. Es otro de estos casos en los que extraño profundamente los opcionales de Scala en Java (los opcionales de Scala son serializables y siguen las pautas de Monad)
Guillaume
37
"el objetivo de diseño principal para Opcional es que se utilice como valor de retorno de funciones cuando un valor de retorno puede estar ausente". Bueno, parece que no puede usarlos para valores de retorno en un EJB remoto. Genial ...
Thilo
15

Se Serializationpueden resolver muchos problemas relacionados desacoplando la forma serializada persistente de la implementación real en tiempo de ejecución en la que 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 clase Optionalimplementa un comportamiento que permite escribir un buen código cuando se trata de valores posiblemente ausentes (en comparación con el uso de null). Pero no agrega ningún beneficio a una representación persistente de sus datos. Solo haría que sus datos serializados fueran más grandes ...

El dibujo anterior puede parecer complicado, pero eso se debe a que muestra el patrón con una sola propiedad. Cuantas más propiedades tenga su clase, más se revelará su simplicidad.

Y no olvidar, la posibilidad de cambiar la implementación de Mycompletamente sin necesidad de adaptar la forma persistente ...

Holger
fuente
2
+1, pero con más campos habrá más texto estándar para copiarlos.
Marko Topolnik
1
@Marko Topolnik: como máximo, una sola línea por propiedad y dirección. Sin embargo, al proporcionar un constructor apropiado class My, lo que normalmente se hace ya que también es útil para otros usos, readResolvepodría ser una implementación de una sola línea, reduciendo así el texto estándar a una sola línea por propiedad. Lo cual no es mucho dado el hecho de que cada propiedad mutable tiene al menos siete líneas de código en la clase de Mytodos modos.
Holger
Escribí una publicación sobre el mismo tema. Es esencialmente la versión de texto largo de esta respuesta: Serializar Opcional
Nicolai
La desventaja es que debe hacer eso para cualquier bean / pojo que use opcionales. Pero aún así, buena idea.
GhostCat
4

Es una omisión curiosa.

Tendría que marcar el campo como transienty proporcionar su propio writeObject()método personalizado que escribió el get()resultado en sí, y un readObject()método que restauró al Optionalleer ese resultado de la secuencia. Sin olvidar llamar defaultWriteObject()y defaultReadObject()respectivamente.

Marqués de Lorne
fuente
Si poseo el código de una clase, es más conveniente almacenar un mero objeto en un campo. La clase opcional en tal caso estaría restringida para la interfaz de la clase (el método get devolvería Optional.ofNullable (campo)). Pero para la representación interna no es posible usar Opcional para intentar claramente que el valor es opcional.
vanarchi
Acabo de demostrar que es posible. Si piensa lo contrario por alguna razón, ¿de qué trata exactamente su pregunta?
Marqués de Lorne
Gracias por tu respuesta, agrega una opción para mí. En mi comentario, quería mostrar más pensamientos sobre este tema, considere Pro y contra de cada solución. En el uso de los métodos writeObject / readObject tenemos una clara intención de Opcional en la representación del estado, pero la implementación de la serialización se vuelve más complicada. Si el campo se usa intensamente en cálculos / flujos, es más conveniente usar writeObject / readObject.
vanarchi
Los comentarios deben ser relevantes para las respuestas que aparecen debajo. La relevancia de tus cavilaciones se me escapa francamente.
Marqués de Lorne
3

La biblioteca Vavr.io (antes Javaslang) también tiene la Optionclase que es serializable:

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

Si desea mantener una lista de tipos más consistente y evitar el uso de null, hay una alternativa chiflada.

Puede almacenar el valor utilizando una intersección de tipos . Junto con una lambda, esto permite algo como:

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

Tener la tempvariable separada evita cerrar sobre el propietario del valuemiembro y, por lo tanto, serializar demasiado.

Dan Gravell
fuente