如何安全地序列化lambda?

Jus*_*tin 8 java lambda serialization java-8

尽管可以在Java 8中序列化lambda,但强烈建议不要这样做.甚至不鼓励对内部类进行序列化.给出的原因是lambdas可能不会在另一个JRE上正确地反序列化.然而,这并不意味着这有一个办法可以安全的序列化拉姆达?

例如,假设我将类定义为:

public class MyClass {
    private String value;
    private Predicate<String> validateValue;

    public MyClass(String value, Predicate<String> validate) {
        this.value = value;
        this.validateValue = validate;
    }

    public void setValue(String value) {
        if (!validateValue(value)) throw new IllegalArgumentException();
        this.value = value;
    }

    public void setValidation(Predicate<String> validate) {
        this.validateValue = validate;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我像这样声明了类的实例,我不应该序列化它:

MyClass obj = new MyClass("some value", (s) -> !s.isEmpty());
Run Code Online (Sandbox Code Playgroud)

但是如果我像这样创建了一个类的实例呢:

// Could even be a static nested class
public class IsNonEmpty implements Predicate<String>, Serializable {
    @Override
    public boolean test(String s) {
        return !s.isEmpty();
    }
}
Run Code Online (Sandbox Code Playgroud)
MyClass isThisSafeToSerialize = new MyClass("some string", new IsNonEmpty());
Run Code Online (Sandbox Code Playgroud)

现在这可以安全地序列化吗?我的直觉说是的,它应该是安全的,因为没有理由认为接口java.util.function应该与任何其他随机接口区别对待.但我仍然保持警惕.

Hol*_*ger 11

这取决于你想要的安全性.事实并非如此,不能在不同的JRE之间共享序列化的lambda.他们有一个明确定义的持久表示,SerializedLambda.当你学习它是如何工作的时候,你会发现它依赖于定义类的存在,它将有一个重建lambda的特殊方法.

使其不可靠的是依赖于编译器特定的工件,例如合成目标方法,它具有一些生成的名称,因此简单的更改(如插入另一个lambda表达式或使用不同的编译器重新编译该类)可能会破坏与现有序列化lambda的兼容性表达.

但是,使用手动编写的类并不能免除这一点.如果没有显式声明serialVersionUID,默认算法将通过散列类工件(包括private合成类工件)来计算id ,从而添加类似的编译器依赖项.所以,如果你想要可靠的持久形式,最小的做法是声明一个明确的serialVersionUID.

或者你转向最强大的形式:

public enum IsNonEmpty implements Predicate<String> {
    INSTANCE;

    @Override
    public boolean test(String s) {
        return !s.isEmpty();
    }
}
Run Code Online (Sandbox Code Playgroud)

序列化此常量不会存储实际实现的任何属性,除了它的类名(enum当然它是一个事实)以及对常量名称的引用.反序列化后,将使用该名称的实际唯一实例.


请注意,可序列化的lambda表达式可能会产生安全性问题,因为它们打开了另一种获取允许调用目标方法的对象的方法.但是,这适用于所有可序列化的类,因为您的问题中显示的所有变体和此答案允许故意反序列化允许调用封装操作的对象.但是通过显式的可序列化类,作者通常更了解这一事实.

  • 究竟.使类可序列化就像添加一个额外的`public`构造函数(或访问器),即使类本身不是`public`也可以使用它.结合像`Predicate`这样的通用接口,它意味着提供对封装操作的访问.如果操作本身并不重要,那就没有问题. (2认同)