Comment sérialiser un lambda?

157

Comment puis-je sérialiser élégamment un lambda?

Par exemple, le code ci-dessous lance un NotSerializableException. Comment puis-je résoudre ce problème sans créer une SerializableRunnableinterface «factice»?

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();
    }
}
assylies
la source
18
Bien que cela soit possible (voir la réponse choisie), tout le monde devrait probablement réfléchir à deux fois avant de le faire. Elle est officiellement «fortement déconseillée» et peut avoir de graves implications en matière de sécurité .
David

Réponses:

260

Java 8 introduit la possibilité de convertir un objet en une intersection de types en ajoutant plusieurs limites . Dans le cas de la sérialisation, il est donc possible d'écrire:

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

Et le lambda devient automatiquement sérialisable.

assylies
la source
4
Très intéressant - cette fonctionnalité semble assez puissante. Existe-t-il une utilisation d'une telle expression en dehors du casting de lambdas? Par exemple, est-il maintenant possible de faire quelque chose de similaire avec une classe anonyme ordinaire?
Balder
6
@Balder La possibilité de transtyper en un type d'intersection a été ajoutée afin de fournir un type cible pour l'inférence de type de lambdas. Puisque les AIC ont un type manifeste (c'est-à-dire que son type n'est pas déduit), il n'est pas utile de convertir une AIC en un type d'intersection. (C'est possible, mais pas utile.) Pour qu'un AIC implémente plusieurs interfaces, vous devez créer une nouvelle sous-interface qui les étend toutes, puis instancier cela.
Stuart marque le
2
Cela produira-t-il un avertissement du compilateur, indiquant qu'aucun serialVersionUID n'est défini?
Kirill Rakhman
11
Remarque: cela ne fonctionne que si vous appliquez le moulage pendant la construction. Ce qui suit lancera une exception ClassCastException: Runnable r = () -> System.out.println ("Serializable!"); Exécutable sérialisableR = (Exécutable et sérialisable) r;
bcody
3
@bcody Oui voir aussi: stackoverflow.com/questions/25391656/…
assylias
24

La même construction peut être utilisée pour les références de méthode. Par exemple ce 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);
    }
}

définit une expression lambda et une référence de méthode avec un type de cible sérialisable.

Vicente Romero
la source
18

Très laid casting. Je préfère définir une extension sérialisable à l'interface fonctionnelle que j'utilise

Par exemple:

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

alors la méthode acceptant le lambda peut être définie comme telle:

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

et en appelant la fonction, vous pouvez passer votre lambda sans aucun cast laid:

someFunction(arg -> doXYZ(arg));
Pascal
la source
2
J'aime cette réponse car tout appelant externe que vous n'écrivez pas sera automatiquement sérialisable. Si vous voulez que les objets soumis soient sérialisables, votre interface doit être sérialisable, ce qui est en quelque sorte le point d'une interface. Cependant, la question disait "sans créer une SerializableRunnableinterface" factice ""
slevin
4

Au cas où quelqu'un tomberait ici lors de la création du code Beam / Dataflow:

Beam a sa propre interface SerializableFunction, donc pas besoin d'interface factice ou de casts détaillés.

Rafaël
la source
3

Si vous êtes prêt à passer à un autre cadre de sérialisation comme Kryo , vous pouvez vous débarrasser des multiples limites ou de l'exigence que l'interface implémentée doit implémenter Serializable. L'approche consiste à

  1. Modifiez le InnerClassLambdaMetafactorypour toujours générer le code requis pour la sérialisation
  2. Appelez directement le LambdaMetaFactorypendant la désérialisation

Pour plus de détails et le code, consultez cet article de blog

ruediste
la source