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

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

Например, код ниже выдает 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, Подход к

  1. Изменить InnerClassLambdaMetafactory всегда генерировать код, необходимый для сериализации
  2. Прямо позвони 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);
}
Другие вопросы по тегам