Como serializar um lambda?

157

Como posso serializar elegantemente um lambda?

Por exemplo, o código abaixo gera a NotSerializableException. Como posso corrigi-lo sem criar uma SerializableRunnableinterface "fictícia"?

public static void main(String[] args) throws Exception {
    File file = Files.createTempFile("lambda", "ser").toFile();
    try (ObjectOutput oo = new ObjectOutputStream(new FileOutputStream(file))) {
        Runnable r = () -> System.out.println("Can I be serialized?");
        oo.writeObject(r);
    }

    try (ObjectInput oi = new ObjectInputStream(new FileInputStream(file))) {
        Runnable  r = (Runnable) oi.readObject();
        r.run();
    }
}
assylias
fonte
18
Embora isso seja possível (veja a resposta selecionada), todos provavelmente devem pensar duas vezes antes de realmente fazer isso. É oficialmente "fortemente desencorajado" e pode ter sérias implicações de segurança .
David David

Respostas:

260

O Java 8 introduz a possibilidade de converter um objeto em uma interseção de tipos adicionando vários limites . No caso de serialização, é possível escrever:

Runnable r = (Runnable & Serializable)() -> System.out.println("Serializable!");

E o lambda se torna automagicamente serializável.

assylias
fonte
4
Muito interessante - esse recurso parece ser bastante poderoso. Existe algum uso dessa expressão de elenco fora do elenco de lambdas? Por exemplo, agora também é possível fazer algo semelhante a uma classe anônima comum?
Balder
6
@Balder A facilidade de converter em um tipo de interseção foi adicionada para fornecer um tipo de destino para a inferência de tipo de lambdas. Como os AICs têm um tipo de manifesto (ou seja, seu tipo não é inferido), converter um AIC em um tipo de interseção não é útil. (É possível, mas não é útil.) Para que um AIC implemente várias interfaces, é necessário criar uma nova subinterface que estenda todas elas e instanciar isso.
Stuart marcas
2
Isso produzirá um aviso do compilador, dizendo que nenhum serialVersionUID está definido?
Kirill Rakhman
11
Nota: isso só funciona se você aplicar o elenco durante a construção. A seguir, será lançada uma ClassCastException: Runnable r = () -> System.out.println ("Serializable!"); Runnable serializableR = (Runnable & Serializable) r;
usar o seguinte comando
3
@bcody Sim, veja também: stackoverflow.com/questions/25391656/…
assylias
24

A mesma construção pode ser usada para referências de método. Por exemplo, este código:

import java.io.Serializable;

public class Test {
    static Object bar(String s) {
        return "make serializable";
    }

    void m () {
        SAM s1 = (SAM & Serializable) Test::bar;
        SAM s2 = (SAM & Serializable) t -> "make serializable";
    }

    interface SAM {
        Object action(String s);
    }
}

define uma expressão lambda e uma referência de método com um tipo de destino serializável.

Vicente Romero
fonte
18

Elenco muito feio. Prefiro definir uma extensão serializável para a interface funcional que estou usando

Por exemplo:

interface SerializableFunction<T,R> extends Function<T,R>, Serializable {}
interface SerializableConsumer<T> extends Consumer<T>, Serializable {}

então o método que aceita o lambda pode ser definido como tal:

private void someFunction(SerializableFunction<String, Object> function) {
   ...
}

e chamando a função, você pode passar seu lambda sem nenhum elenco feio:

someFunction(arg -> doXYZ(arg));
Pascal
fonte
2
Gosto dessa resposta, pois qualquer chamador externo que você não escrever será automaticamente serializável. Se você deseja que os objetos enviados sejam serializáveis, sua interface deve ser serializável, que é o tipo de objetivo de uma interface. No entanto, a pergunta dizia "sem criar uma SerializableRunnableinterface" fictícia ""
slevin
4

Caso alguém caia aqui ao criar o código Beam / Dataflow:

O Beam possui sua própria interface SerializableFunction, portanto, não há necessidade de interface fictícia ou elencos detalhados.

Rafael
fonte
3

Se você deseja mudar para outra estrutura de serialização como o Kryo , pode se livrar dos múltiplos limites ou do requisito que a interface implementada deve implementar Serializable. A abordagem é

  1. Modifique o InnerClassLambdaMetafactory para sempre gerar o código necessário para serialização
  2. Chamar diretamente o LambdaMetaFactorydurante a desserialização

Para detalhes e código, consulte esta postagem do blog

ruediste
fonte