Jak serializować lambdę?

157

Jak mogę elegancko serializować lambdę?

Na przykład poniższy kod generuje plik NotSerializableException. Jak mogę to naprawić bez tworzenia SerializableRunnable„fikcyjnego” interfejsu?

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();
    }
}
asylias
źródło
18
Chociaż jest to możliwe (zobacz wybraną odpowiedź), każdy powinien prawdopodobnie dwa razy pomyśleć, aby to zrobić. Oficjalnie jest to „zdecydowanie odradzane” i może mieć poważne konsekwencje dla bezpieczeństwa .
David

Odpowiedzi:

260

Java 8 wprowadza możliwość rzutowania obiektu na przecięcie typów przez dodanie wielu granic . W przypadku serializacji można więc napisać:

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

A lambda automatycznie staje się serializowalna.

asylias
źródło
4
Bardzo interesujące - ta funkcja wydaje się być dość potężna. Czy jest jakieś zastosowanie takiego wyrażenia rzutowania poza rzutowaniem lambd? Np. Czy można teraz zrobić coś podobnego ze zwykłą klasą anonimową?
Balder
6
@Balder Dodano możliwość rzutowania na typ przecięcia w celu zapewnienia typu docelowego dla wnioskowania o typie lambd. Ponieważ AIC mają typ manifestu (tj. Jego typ nie jest wywnioskowany) rzutowanie AIC na typ przecięcia nie jest przydatne. (Jest to możliwe, po prostu nieprzydatne). Aby mieć AIC zaimplementować wiele interfejsów, musisz utworzyć nowy podinterfejs, który rozszerzy je wszystkie, a następnie utworzyć instancję.
Stuart Marks
2
Czy spowoduje to wyświetlenie ostrzeżenia kompilatora, mówiącego, że nie zdefiniowano identyfikatora serialVersionUID?
Kirill Rakhman,
11
Uwaga: działa to tylko wtedy, gdy zastosujesz odlew podczas budowy. Poniższy przykład spowoduje zgłoszenie wyjątku ClassCastException: Runnable r = () -> System.out.println ("Serializable!"); Runnable serializableR = (Runnable & serializable) r;
bcody
3
@bcody Tak, zobacz także: stackoverflow.com/questions/25391656/…
assylias
24

Tej samej konstrukcji można użyć do odniesień do metod. Na przykład ten kod:

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

definiuje wyrażenie lambda i odwołanie do metody z możliwym do serializacji typem docelowym.

Vicente Romero
źródło
18

Bardzo brzydka obsada. Wolę zdefiniować rozszerzenie z możliwością serializacji do interfejsu funkcjonalnego, którego używam

Na przykład:

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

wówczas metodę akceptującą lambdę można zdefiniować następująco:

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

i wywołując funkcję, którą możesz przekazać swoją lambdę bez brzydkiego rzutowania:

someFunction(arg -> doXYZ(arg));
Pascal
źródło
2
Podoba mi się ta odpowiedź, ponieważ wtedy każdy zewnętrzny rozmówca, którego nie napiszesz, będzie również automatycznie serializowany. Jeśli chcesz, aby przesłane obiekty były możliwe do serializacji, interfejs powinien być możliwy do serializacji, co jest swego rodzaju punktem interfejsu. Jednak pytanie SerializableRunnable
brzmiało
4

Na wypadek, gdyby ktoś upadł tutaj podczas tworzenia kodu Beam / Dataflow:

Beam ma własny interfejs SerializableFunction, więc nie ma potrzeby stosowania fikcyjnego interfejsu ani pełnych rzutów.

Rafaël
źródło
3

Jeśli chcesz przełączyć się na inną platformę serializacji, taką jak Kryo , możesz pozbyć się wielu ograniczeń lub wymagań, które musi implementować zaimplementowany interfejs Serializable. Podejście to

  1. Zmodyfikuj, InnerClassLambdaMetafactoryaby zawsze generował kod wymagany do serializacji
  2. Bezpośrednio wywołaj LambdaMetaFactorypodczas deserializacji

Aby uzyskać szczegółowe informacje i kod, zobacz ten wpis na blogu

ruediste
źródło