Come serializzare un lambda?


157

Come posso serializzare elegantemente una lambda?

Ad esempio, il codice seguente genera a NotSerializableException. Come posso ripararlo senza creare un'interfaccia SerializableRunnable"fittizia"?

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

18
Sebbene ciò sia possibile (vedi la risposta selezionata), probabilmente tutti dovrebbero pensarci due volte su come farlo effettivamente. È ufficialmente "fortemente scoraggiato" e può avere serie implicazioni per la sicurezza .
David

Risposte:


260

Java 8 introduce la possibilità di trasmettere un oggetto a un'intersezione di tipi aggiungendo più limiti . Nel caso della serializzazione, è quindi possibile scrivere:

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

E la lambda diventa automagicamente serializzabile.


4
Molto interessante: questa funzione sembra essere abbastanza potente. C'è un uso di un'espressione del genere al di fuori del casting lambdas? Ad esempio, ora è anche possibile fare qualcosa di simile con una normale classe anonima?
Balder

6
@Balder È stata aggiunta la funzione per lanciare un tipo di intersezione al fine di fornire un tipo di destinazione per l'inferenza del tipo di lambda. Poiché gli AIC hanno un tipo manifest (ovvero, il suo tipo non viene dedotto) il cast di un AIC su un tipo di intersezione non è utile. (È possibile, ma non utile.) Per avere un AIC implementare più interfacce, è necessario creare una nuova interfaccia secondaria che le estenda tutte e quindi creare un'istanza.
Stuart segna il

2
Questo produrrà un avviso del compilatore, dicendo che non è definito serialVersionUID?
Kirill Rakhman,

11
Nota: funziona solo se si applica il cast durante la costruzione. Quanto segue genererà una ClassCastException: Runnable r = () -> System.out.println ("Serializable!"); Runableable serializableR = (Runnable & Serializable) r;
bcody


24

La stessa costruzione può essere utilizzata per riferimenti a metodi. Ad esempio questo codice:

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

definisce un'espressione lambda e un riferimento al metodo con un tipo di destinazione serializzabile.


18

Cast molto brutto. Preferisco definire un'estensione serializzabile per l'interfaccia funzionale che sto usando

Per esempio:

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

quindi il metodo che accetta lambda può essere definito come tale:

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

e chiamando la funzione puoi passare il tuo lambda senza alcun brutto cast:

someFunction(arg -> doXYZ(arg));

2
Mi piace questa risposta perché in questo modo anche qualsiasi chiamante esterno che non scrivi sarà automaticamente serializzabile. Se vuoi che gli oggetti inviati siano serializzabili, la tua interfaccia dovrebbe essere serializzabile, che è un po 'il punto di un'interfaccia. Tuttavia, la domanda diceva "senza creare un'interfaccia SerializableRunnable" fittizia "
slevin

4

Nel caso in cui qualcuno cada qui durante la creazione del codice Beam / Dataflow:

Beam ha la sua interfaccia SerializableFunction, quindi non c'è bisogno di interfaccia fittizia o cast verbose.


3

Se sei disposto a passare a un altro framework di serializzazione come Kryo , puoi sbarazzarti dei limiti multipli o dei requisiti che l'interfaccia implementata deve implementare Serializable. L'approccio è quello di

  1. Modifica il InnerClassLambdaMetafactory per generare sempre il codice richiesto per la serializzazione
  2. Chiamare direttamente LambdaMetaFactorydurante la deserializzazione

Per dettagli e codice vedi questo post sul blog

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.