Как сериализовать лямбду?
Как я могу элегантно сериализовать лямбду?
Например, код ниже выдает 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();
}
}
6 ответов
Java 8 предоставляет возможность привести объект к пересечению типов путем добавления нескольких границ. Поэтому в случае сериализации можно написать:
Runnable r = (Runnable & Serializable)() -> System.out.println("Serializable!");
И лямбда автоматически становится сериализуемой.
Очень безобразный актерский состав. Я предпочитаю определять 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));
Та же самая конструкция может использоваться для ссылок на метод. Например этот код:
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);
}
}
определяет лямбда-выражение и ссылку на метод с сериализуемым целевым типом.
Если кто-то упадет сюда при создании кода Beam/Dataflow:
Beam имеет собственный интерфейс SerializableFunction, поэтому нет необходимости в фиктивном интерфейсе или подробных приведениях.
Если вы хотите переключиться на другую платформу сериализации, такую как Kryo, вы можете избавиться от нескольких границ или требования, которое должен реализовывать реализованный интерфейс. Serializable
, Подход к
- Изменить
InnerClassLambdaMetafactory
всегда генерировать код, необходимый для сериализации - Прямо позвони
LambdaMetaFactory
во время десериализации
Подробности и код смотрите в этой записи блога.
Чтобы добавить к другим ответам, вы можете создать сериализуемую лямбду, используя выражение приведения или используя новый интерфейс, расширяющий Serializable, как показано в других ответах. Обратите внимание, что полученные объекты не обязательно хорошо сочетаются с методами функциональных интерфейсов по умолчанию (например). Третье решение, далекое от идеального, поскольку оно не гарантирует сериализуемости правого операнда, создает сериализуемый объект.
@Test
public void testCasetExpression() {
Predicate<Integer> p1 = (Predicate<Integer> & Serializable)i -> true;
Predicate<Integer> p2 = (Predicate<Integer> & Serializable)i -> true;
Predicate<Integer> p3 = p1.and(p2);
Assertions.assertFalse(p3 instanceof Serializable); // Notice false
}
interface SerializablePredicateV1<T> extends Predicate<T>, Serializable {}
@Test
public void testInterfaceV1() {
Predicate<Integer> p1 = (SerializablePredicateV1)i -> true;
Predicate<Integer> p2 = (SerializablePredicateV1)i -> true;
Predicate<Integer> p3 = p1.and(p2);
Assertions.assertFalse(p3 instanceof Serializable); // Notice false
}
interface SerializablePredicateV2<T> extends Predicate<T>, Serializable {
@Override
default SerializablePredicateV2<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
}
@Test
public void testInterfaceV2() {
Predicate<Integer> p1 = (SerializablePredicateV2)i -> true;
Predicate<Integer> p2 = (SerializablePredicateV2)i -> true;
Predicate<Integer> p3 = p1.and(p2);
Assertions.assertTrue(p3 instanceof Serializable); // Notice true
Predicate<Integer> p4 = i -> true; // Not serializable
Predicate<Integer> p5 = p1.and(p4);
// true but will fail because p4 is not serializable
Assertions.assertTrue(p5 instanceof Serializable);
}
// Not a Predicate anymore :(
interface SerializablePredicateV3<T> extends Serializable {
boolean test(T t);
default SerializablePredicateV3<T> and(SerializablePredicateV3<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
}
@Test
public void testInterfaceV3() {
SerializablePredicateV3<Integer> p1 = (SerializablePredicateV3)i -> true;
SerializablePredicateV3<Integer> p2 = (SerializablePredicateV3)i -> true;
SerializablePredicateV3<Integer> p3 = p1.and(p2);
}