Как сериализовать лямбду?

157

Как я могу элегантно сериализовать лямбду?

Например, код ниже выдает a NotSerializableException. Как я могу это исправить, не создавая SerializableRunnable"фиктивный" интерфейс?

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
источник
18
Хотя это возможно (см. Выбранный ответ), каждый, вероятно, должен дважды подумать о том, как на самом деле это сделать. Это официально "решительно не рекомендуется" и может иметь серьезные последствия для безопасности .
Дэвид

Ответы:

260

Java 8 предоставляет возможность привести объект к пересечению типов путем добавления нескольких границ . Поэтому в случае сериализации можно написать:

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

И лямбда автоматически становится сериализуемой.

assylias
источник
4
Очень интересно - эта функция кажется довольно мощной. Есть ли какое-либо использование такого выражения вне кастинга лямбд? Например, теперь возможно ли сделать нечто подобное с обычным анонимным классом?
Balder
6
@Balder Добавлена ​​возможность приведения к типу пересечения, чтобы обеспечить целевой тип для вывода типа лямбд. Поскольку AIC имеют тип манифеста (т. Е. Его тип не выводится), приведение AIC к типу пересечения бесполезно. (Это возможно, просто бесполезно.) Чтобы AIC реализовывал несколько интерфейсов, вам нужно создать новый подинтерфейс, который расширяет их все, а затем создать его экземпляр.
Стюарт Маркс
2
Будет ли это выдавать предупреждение компилятора о том, что не задан serialVersionUID?
Кирилл Рахман
11
Примечание: это работает, только если вы применяете приведение во время строительства. Следующее вызовет исключение ClassCastException: Runnable r = () -> System.out.println («Сериализуемый!»); Runnable serializableR = (Runnable & Serializable) r;
bcody
3
@bcody Да, смотрите также: stackoverflow.com/questions/25391656/…
assylias
24

Та же самая конструкция может использоваться для ссылок на метод. Например этот код:

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

определяет лямбда-выражение и ссылку на метод с сериализуемым целевым типом.

Висенте Ромеро
источник
18

Очень безобразный актерский состав. Я предпочитаю определять Serializable расширение для функционального интерфейса, который я использую

Например:

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

тогда метод, принимающий лямбду, можно определить так:

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

и вызывая функцию, вы можете передать свою лямбду без какого-либо уродливого преобразования

someFunction(arg -> doXYZ(arg));
паскаль
источник
2
Мне нравится этот ответ, потому что тогда любой внешний вызывающий абонент, которого вы не напишите, автоматически также будет сериализуем. Если вы хотите, чтобы переданные объекты были сериализуемыми, ваш интерфейс должен быть сериализуемым, что является своего рода точкой интерфейса. Тем не менее, вопрос действительно сказал «без создания SerializableRunnable« фиктивного »интерфейса»
slevin
4

Если кто-то попадет сюда при создании кода Beam / Dataflow:

Beam имеет свой собственный интерфейс SerializableFunction, поэтому нет необходимости в фиктивном интерфейсе или подробном приведении.

Рафаэль
источник
3

Если вы хотите перейти на другую платформу сериализации, такую ​​как Kryo , вы можете избавиться от нескольких границ или требований, которые должен реализовывать реализованный интерфейс Serializable. Подход к

  1. Изменить, InnerClassLambdaMetafactoryчтобы всегда генерировать код, необходимый для сериализации
  2. Прямо позвоните во LambdaMetaFactoryвремя десериализации

Подробности и код смотрите в этой записи блога.

ruediste
источник