如何序列化一个lambda表达式?

174

如何优雅地序列化一个lambda表达式?

例如,下面的代码会抛出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();
    }
}

18
虽然这是有可能的(请参见所选答案),但每个人都应该三思而后行。官方上强烈不建议这样做,并且可能会有严重的安全隐患(请参见链接)。请注意不要改变原文的意思。 - David
6个回答

296
Java 8引入了将一个对象转换为多个边界类型的交集的可能性。在序列化的情况下,因此可以编写以下代码:
Runnable r = (Runnable & Serializable)() -> System.out.println("Serializable!");

而且Lambda表达式会自动变成可序列化的。


4
很有趣——这个特性似乎非常强大。在将 lambda 表达式转换的情况之外,这种转换表达式有什么用途吗?例如,现在是否也可以使用普通的匿名类做类似的事情? - Balder
6
为了推断Lambda表达式的类型,添加了将类型转换为交叉类型的功能。由于AIC(自动闭包实例)具有明确的类型(即其类型不是推断得出的),因此将AIC强制转换为交叉类型没有用处(虽然可能是可行的)。要使AIC实现多个接口,您必须创建一个新的子接口,该接口扩展所有这些接口,然后实例化它。 - Stuart Marks
2
@cypressious 没有编译器警告。 - assylias
16
请注意:只有在构造期间应用强制类型转换才有效。以下代码会抛出ClassCastException异常:Runnable r = () -> System.out.println("Serializable!"); Runnable serializableR = (Runnable & Serializable)r; - bcody
3
是的,请参考:https://dev59.com/yV8e5IYBdhLWcg3wwMr7 - assylias
显示剩余8条评论

29

非常丑陋的强制类型转换。我更喜欢定义一个可序列化的扩展功能接口。

例如:

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

那么接受lambda的方法可以定义如下:
private void someFunction(SerializableFunction<String, Object> function) {
   ...
}

通过调用函数,您可以传递lambda而不需要任何丑陋的转换:

someFunction(arg -> doXYZ(arg));

4
我喜欢这个答案,因为这样任何未被你编写的外部调用者也会自动成为可序列化的。如果你想让提交的对象可序列化,你的接口应该是可序列化的,这也是接口的一个重点。然而,问题确实说了“不创建'SerializableRunnable'虚拟接口”。 - slevin

25

同样的构造可以用于方法引用。例如,这段代码:

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

定义一个具有可序列化目标类型的lambda表达式和方法引用。


8

如果在创建Beam/Dataflow代码时有人遇到问题:

Beam拥有自己的SerializableFunction接口,因此不需要虚拟接口或冗长的转换。


4
如果您愿意切换到另一个序列化框架,如Kryo,则可以摆脱多个边界或要求实现的接口必须实现Serializable的要求。方法是:

  1. 修改InnerClassLambdaMetafactory以始终生成所需的序列化代码
  2. 在反序列化期间直接调用LambdaMetaFactory

有关详细信息和代码,请参见此博客文章


1
该文章已移动至此处:https://ruediste.github.io/java/kryo/2017/05/07/serializing-non-serializable-lambdas.html - Danon

0
除了其他答案之外,您可以通过使用强制转换表达式或使用扩展Serializable接口的新接口来创建可序列化的lambda,如其他答案所示。请注意,生成的对象不一定与函数接口的默认方法相容(例如)。第三种解决方案远非理想,因为它不能确保右操作数是可序列化的,但它确实生成了一个可序列化的对象。
@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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接