【问题标题】:"Invalid lambda deserialization" when lambda used for interface implementation当 lambda 用于接口实现时“无效的 lambda 反序列化”
【发布时间】:2015-03-09 16:10:56
【问题描述】:

我有一个带有 lambda 表达式的检票口项目。在用户单击后退按钮的一页上,我的应用程序崩溃:

java.lang.IllegalArgumentException: Invalid lambda deserialization
at x.y.z.MyPage$3.$deserializeLambda$(MyPage.java:1)

在页面类(我返回的地方)中,我使用 lambda 表达式来实现这个接口:

public interface Localizator extends Serializable {
    String getLocalizedString(String key);
}

还有 lambda:

protected void someMethod() {
    localize((String key) -> getString(key));
}

当我将 lambda 更改为匿名类时,一切正常。在这种情况下我应该如何使用 lambda?

环境:Java 1.8.0_25、Netbeans 8.0.2、Wicket 6.17.0。

编辑: 这是使用 lambda 的真实(但简化)方法:

@Override
protected DataLoader createDataLoader() {

    return new DataLoader(){

        @Override
        public List loadData() {
            ...
        }

        @Override
        public List convertToTableRows(List data) {
            return Converter.asRowList(
                data, 
                (Record record) -> {...}, // this lambda is OK
                (String key) -> getString(key)); // this lambda is crashing
        }

        @Override
        public List filterTableRow() {
            ...
        }

    };
}

转换器类:

public class Converter implements Serializable { 
    public static ArrayList asRowList(List data, OtherLoader loader, Localizator localizator){...}

DataLoader 还扩展了 Serializable。

【问题讨论】:

  • 您的localize() 方法看起来如何?
  • 其实localize方法只是一个例子,lambda用在更复杂的上下文中,我会编辑我的问题来展示它在现实中是如何使用的。
  • 是的,以这种方式转换 lambda (Localizator & Serializable)(String key) -> getString(key) 有帮助,但我不明白为什么我必须将可序列化接口的 lambda 转换为可序列化。我是否必须在每个页面中将每个 lambda 转换为可序列化?这毫无意义……
  • 当函数 interface 扩展 Serializable 时,不需要额外的演员表。确保类是同步的,例如您没有针对尚未扩展Serializableinterface 版本编译MyPage.java,并且您没有尝试使用MyPage 类的不同版本进行反序列化。

标签: lambda wicket java-8


【解决方案1】:

要启用Serialization 支持,将interface 扩展为Serializable 就足够了。那时不需要进行额外的演员表。您的代码可以序列化 lambda 表达式实例这一事实表明了这一点。 反序列化时失败,这是类不兼容的指标,因此如果插入强制转换有帮助,很可能是副作用,因为插入强制转换强制重新编译包含 lambda 的类表达。


如果您想继续使用序列化 lambda,您应该了解它的工作原理,如 the documentation of SerializedLambda 中所述:

可序列化 lambda 的实现者(例如编译器或语言运行时库)应确保实例正确反序列化。这样做的一种方法是确保writeReplace 方法返回SerializedLambda 的实例,而不是允许继续进行默认序列化。

SerializedLambda 有一个readResolve 方法,该方法在捕获类中查找名为$deserializeLambda$(SerializedLambda) 的(可能是私有的)静态方法,将其自身作为第一个参数调用,并返回结果。实现 $deserializeLambda$ 的 Lambda 类负责验证 SerializedLambda 的属性是否与该类实际捕获的 lambda 一致。

因此,当$deserializeLambda$ 以异常拒绝反序列化时,这意味着序列化 lambda 的属性与类中定义的已知可序列化 lambda 的属性不匹配。这些属性可能会变得非常脆弱。

它们包括功能接口、捕获的参数和目标方法。在 lambda 表达式(而不是方法引用)的情况下,目标方法是定义类中的合成方法,具有编译器选择的未指定名称,甚至可能依赖于与插入另一个表达式相同的类可能会由于编号方案而导致名称更改。

与插入serialVersionUID 的普通类不同,可能会告诉序列化框架它不应该关心明显的不一致,你不能告诉 lambda 表达式显示更多的健壮性。换句话说,一旦你序列化了应该持久化的 lambda 实例,你不能改变它们的定义类。另见this answer

【讨论】:

    【解决方案2】:

    这并不能回答上面的问题,但是在搜索Invalid lambda deserialization in Flink 时会首先弹出这个帖子。

    如果在发布前使用 Proguard 进行混淆

    • Proguard 搞砸了 Serializable

    • 您需要配置proguard.pro 来修复此错误,请参阅this answer

    在我自己的情况下,我实现了一段时间前official documentation 中建议的“Java 8 Lambda”设置,但文档发生了变化,现在我错过了那里解释的新设置。

    【讨论】:

    • 我希望这种“链接另一个问题”的方式是可以的。这个参考资料可以让我节省一天的时间来了解哪个 lambda 是不可序列化的,而实际上一切都很好(只有混淆破坏了事情)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-22
    • 1970-01-01
    • 1970-01-01
    • 2018-06-25
    • 1970-01-01
    • 2023-04-07
    • 2019-10-03
    相关资源
    最近更新 更多