Wie serialisiere ich ein Lambda?

157

Wie kann ich ein Lambda elegant serialisieren?

Der folgende Code löst beispielsweise a aus NotSerializableException. Wie kann ich das Problem beheben, ohne eine SerializableRunnable"Dummy" -Schnittstelle zu erstellen?

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();
    }
}
Assylien
quelle
18
Während dies möglich ist (siehe die ausgewählte Antwort), sollte jeder wahrscheinlich zweimal darüber nachdenken, dies tatsächlich zu tun. Es wird offiziell "stark abgeraten" und kann schwerwiegende Auswirkungen auf die Sicherheit haben .
David

Antworten:

260

Java 8 bietet die Möglichkeit, ein Objekt durch Hinzufügen mehrerer Grenzen in einen Schnittpunkt von Typen umzuwandeln . Bei der Serialisierung ist es daher möglich zu schreiben:

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

Und das Lambda wird automatisch serialisierbar.

Assylien
quelle
4
Sehr interessant - diese Funktion scheint ziemlich mächtig zu sein. Gibt es eine Verwendung eines solchen Cast-Ausdrucks außerhalb des Castings von Lambdas? Ist es jetzt beispielsweise auch möglich, mit einer normalen anonymen Klasse etwas Ähnliches zu tun?
Balder
6
@Balder Die Funktion zum Umwandeln in einen Kreuzungstyp wurde hinzugefügt, um einen Zieltyp für die Typinferenz von Lambdas bereitzustellen. Da AICs einen Manifesttyp haben (dh ihr Typ wird nicht abgeleitet), ist es nicht sinnvoll, einen AIC in einen Schnittpunkttyp umzuwandeln. (Es ist möglich, aber nicht nützlich.) Damit ein AIC mehrere Schnittstellen implementiert, müssen Sie eine neue Unterschnittstelle erstellen, die alle erweitert, und diese dann instanziieren.
Stuart Marks
2
Wird dadurch eine Compiler-Warnung ausgegeben, die besagt, dass keine serialVersionUID definiert ist?
Kirill Rakhman
11
Hinweis: Dies funktioniert nur, wenn Sie den Guss während der Konstruktion anwenden. Folgendes löst eine ClassCastException aus: Runnable r = () -> System.out.println ("Serializable!"); Runnable serializableR = (Runnable & Serializable) r;
bcody
3
@bcody Ja, siehe auch: stackoverflow.com/questions/25391656/…
Assylias
24

Die gleiche Konstruktion kann für Methodenreferenzen verwendet werden. Zum Beispiel dieser Code:

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

definiert einen Lambda-Ausdruck und eine Methodenreferenz mit einem serialisierbaren Zieltyp.

Vicente Romero
quelle
18

Sehr hässliche Besetzung. Ich bevorzuge es, eine serialisierbare Erweiterung für die von mir verwendete Funktionsschnittstelle zu definieren

Beispielsweise:

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

dann kann die Methode, die das Lambda akzeptiert, als solche definiert werden:

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

Wenn Sie die Funktion aufrufen, können Sie Ihr Lambda ohne hässliche Besetzung übergeben:

someFunction(arg -> doXYZ(arg));
Pascal
quelle
2
Ich mag diese Antwort, weil dann jeder externe Anrufer, den Sie nicht schreiben, automatisch auch serialisierbar ist. Wenn Sie möchten, dass gesendete Objekte serialisierbar sind, sollte Ihre Schnittstelle serialisierbar sein. Dies ist eine Art Punkt einer Schnittstelle. Die Frage SerializableRunnable
lautete
4

Falls jemand beim Erstellen des Beam / Dataflow-Codes hierher fällt:

Beam verfügt über eine eigene SerializableFunction- Schnittstelle, sodass keine Dummy-Schnittstelle oder ausführliche Casts erforderlich sind.

Rafaël
quelle
3

Wenn Sie bereit sind, zu einem anderen Serialisierungsframework wie Kryo zu wechseln , können Sie die Mehrfachgrenzen oder die Anforderung, die die implementierte Schnittstelle implementieren muss, beseitigen Serializable. Der Ansatz ist zu

  1. Ändern Sie das, InnerClassLambdaMetafactoryum immer den für die Serialisierung erforderlichen Code zu generieren
  2. Rufen Sie die LambdaMetaFactorywährend der Deserialisierung direkt an

Details und Code finden Sie in diesem Blogbeitrag

ruediste
quelle