Por que java.util.Optional não é serializável, como serializar o objeto com tais campos

107

A classe Enum é Serializable, portanto, não há problema em serializar o objeto com enums. O outro caso é onde a classe possui campos da classe java.util.Optional. Nesse caso, a seguinte exceção é lançada: java.io.NotSerializableException: java.util.Optional

Como lidar com essas classes, como serializá-las? É possível enviar tais objetos para EJB Remoto ou por RMI?

Este é o exemplo:

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
fonte
Se Optionalfoi marcado como Serializable, o que aconteceria se get()retornasse algo que não fosse serializável?
WW.
10
@WW. Você obteria um é NotSerializableException,claro.
Marquês de Lorne
7
@WW. É como coleções. A maioria das classes de coleção é serializável, mas uma instância de coleção só pode realmente ser serializada se cada objeto contido na coleção também for serializável.
Stuart Marks
Minha opinião pessoal aqui: não é para lembrar as pessoas de testar a unidade de forma adequada até mesmo a serialização de objetos. Acabei de encontrar java.io.NotSerializableException (java.util.Optional) me ;-(
GhostCat

Respostas:

171

Essa resposta é uma resposta à pergunta do título, "O opcional não deve ser serializável?" A resposta curta é que o grupo de especialistas Java Lambda (JSR-335) considerou e rejeitou . Essa nota, e esta e esta indicam que o objetivo principal do projeto Optionalé ser usado como o valor de retorno de funções quando um valor de retorno pode estar ausente. A intenção é que o chamador verifique imediatamente Optionale extraia o valor real, se estiver presente. Se o valor estiver ausente, o chamador pode substituir um valor padrão, lançar uma exceção ou aplicar alguma outra política. Isso normalmente é feito encadeando chamadas de método fluentes no final de um pipeline de fluxo (ou outros métodos) que retornam Optionalvalores.

Nunca foi planejado para Optionalser usado de outras maneiras, como para argumentos de método opcionais ou para ser armazenado como um campo em um objeto . E, por extensão, tornar Optionalserializável permitiria que ele fosse armazenado de forma persistente ou transmitido através de uma rede, o que encoraja usos muito além de seu objetivo de design original.

Normalmente, existem maneiras melhores de organizar os dados do que armazenar um Optionalem um campo. Se um getter (como o getValuemétodo em questão) retorna o valor real Optionaldo campo, ele força cada chamador a implementar alguma política para lidar com um valor vazio. Isso provavelmente levará a um comportamento inconsisente entre os chamadores. Geralmente, é melhor fazer com que os conjuntos de códigos desse campo apliquem alguma política no momento em que são definidos.

Às vezes, as pessoas querem colocar Optionalem coleções, como List<Optional<X>>ou Map<Key,Optional<Value>>. Geralmente, isso também é uma má ideia. Muitas vezes é melhor substituir esses usos de Optionalpor valores de Objeto Nulo (não reaisnull referências ) ou simplesmente omitir totalmente essas entradas da coleção.

Stuart Marks
fonte
26
Muito bem, Stuart. Devo dizer que é uma cadeia extraordinária de raciocínio e são coisas extraordinárias para projetar uma classe que não se destina a ser usada como um tipo de membro de instância. Especialmente quando isso não está declarado no contrato de classe no Javadoc. Talvez eles devessem ter projetado uma anotação em vez de uma classe.
Marquês de Lorne
1
Esta é uma excelente postagem de blog sobre a lógica por trás da resposta de Sutart
Wesley Hartford
2
Ainda bem que não preciso usar campos serializáveis. Caso contrário, isso seria um desastre
Kurru
48
Resposta interessante, para mim essa escolha de design foi completamente inaceitável e equivocada. Você diz "Normalmente existem maneiras melhores de organizar os dados do que armazenar um opcional em um campo", claro, talvez, por que não, mas essa deve ser a escolha do designer, não da linguagem. É outro desses casos em que sinto muita falta dos opcionais do Scala em Java (os opcionais do Scala são serializáveis ​​e seguem as diretrizes do Monad)
Guillaume
37
"o objetivo principal do design para Optional é ser usado como o valor de retorno de funções quando um valor de retorno pode estar ausente.". Bem, parece que você não pode usá-los para valores de retorno em um EJB remoto. Ótimo ...
Thilo
15

Muitos Serializationproblemas relacionados podem ser resolvidos desacoplando o formulário serializado persistente da implementação de tempo de execução real em que você 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;
    }
}

A classe Optionalimplementa um comportamento que permite escrever um bom código ao lidar com valores possivelmente ausentes (em comparação com o uso de null). Mas isso não adiciona nenhum benefício a uma representação persistente de seus dados. Isso apenas aumentaria seus dados serializados ...

O esboço acima pode parecer complicado, mas isso porque demonstra o padrão com apenas uma propriedade. Quanto mais propriedades sua classe tiver, mais sua simplicidade deve ser revelada.

E não se esqueça, a possibilidade de alterar a implementação de Mycompletamente sem qualquer necessidade de adaptar a forma persistente…

Holger
fonte
2
+1, mas com mais campos haverá mais clichês para copiá-los.
Marko Topolnik
1
@Marko Topolnik: no máximo, uma única linha por propriedade e direção. No entanto, ao fornecer um construtor apropriado para class My, o que você geralmente faz porque é útil para outros usos também, readResolvepoderia ser uma implementação de linha única, reduzindo assim o clichê a uma única linha por propriedade. O que não é muito, dado o fato de que cada propriedade mutável tem pelo menos sete linhas de código na classe My.
Holger
Eu escrevi um post sobre o mesmo assunto. É essencialmente a versão de texto longo desta resposta: Serializar opcional
Nicolai
A desvantagem é: você precisa fazer isso para qualquer bean / pojo que usa opcionais. Mesmo assim, boa ideia.
GhostCat
12

Se você quiser um opcional serializável, considere usar o opcional do guava, que é serializável.

Eric Hartford
fonte
4

É uma omissão curiosa.

Você teria que marcar o campo como transiente fornecer seu próprio writeObject()método customizado que escreveu o get()próprio resultado e um readObject()método que restaurou o Optionalao ler esse resultado do fluxo. Não esquecendo de ligar defaultWriteObject()e defaultReadObject()respectivamente.

Marquês de Lorne
fonte
Se eu possuir o código de uma classe, é mais conveniente armazenar um mero objeto em um campo. A classe opcional nesse caso seria restrita para a interface da classe (o método get retornaria Optional.ofNullable (campo)). Mas para a representação interna não é possível usar Opcional para intentar claramente que o valor é opcional.
vanarchi
Acabei de mostrar que é possível. Se você pensa o contrário por algum motivo, sobre o que exatamente é sua pergunta?
Marquês de Lorne
Obrigado pela sua resposta, adiciona uma opção para mim. No meu comentário, gostaria de mostrar mais reflexões sobre este tópico, considere Pró e Contra de cada solução. Nos métodos writeObject / readObject de uso, temos a intenção clara de Optional na representação de estado, mas a implementação da serialização se torna mais complicada. Se o campo for usado intensivamente em cálculos / fluxos - é mais conveniente usar writeObject / readObject.
vanarchi
Os comentários devem ser relevantes para as respostas em que aparecem. A relevância de suas reflexões me escapa francamente.
Marquês de Lorne
3

A biblioteca Vavr.io (antigo Javaslang) também tem a Optionclasse que é serializável:

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

Se você deseja manter uma lista de tipos mais consistente e evitar o uso de null, há uma alternativa estranha.

Você pode armazenar o valor usando uma interseção de tipos . Juntamente com um lambda, isso 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();

Ter a tempvariável separada evita fechar sobre o proprietário do valuemembro e, portanto, serializar demais.

Dan Gravell
fonte