JDK中Lambda表达式的序列化与SerializedLambda的巧妙使用

前提

笔者在下班空余时间想以 Javassist 为核心基于 JDBC 写一套摒弃反射调用的轻量级的 ORM框架,过程中有研读 mybatis 、 tk-mapper 、 mybatis-plus 和 spring-boot-starter-jdbc 的源代码,其中发现了 mybatis-plus 中的 LambdaQueryWrapper 可以获取当前调用的 Lambda 表达式中的方法信息(实际上是 CallSite 的信息),这里做一个完整的记录。本文基于 JDK11 编写,其他版本的 JDK 不一定合适。

神奇的Lambda表达式序列化

之前在看 Lambda 表达式源码实现的时候没有细看 LambdaMetafactory 的注释,这个类顶部大量注释中其中有一段如下:

简单翻译一下就是:可序列化特性。一般情况下,生成的函数对象(这里应该是特指基于 Lambda 表达式实现的特殊函数对象)不需要支持序列化特性。如果需要支持该特性, FLAG_SERIALIZABLE ( LambdaMetafactory 的一个静态整型属性,值为 1 << 0 )可以用来表示函数对象是序列化的。一旦使用了支持序列化特性的函数对象,那么它们以 SerializedLambda 类的形式序列化,这些 SerializedLambda 实例需要额外的"捕获类"的协助(捕获类,如 MethodHandles.Lookup 的 caller 参数所描述),详细信息参阅 SerializedLambda 。

在 LambdaMetafactory 的注释中再搜索一下 FLAG_SERIALIZABLE ,可以看到这段注释:

大意为:设置了 FLAG_SERIALIZABLE 标记后生成的函数对象实例会实现 Serializable 接口,并且会存在一个名字为 writeReplace 的方法,该方法的返回值类型为 SerializedLambda 。调用这些函数对象的方法(前面提到的"捕获类")的调用者必须存在一个名字为 $deserializeLambda$的方法,如 SerializedLambda 类所描述。

最后看 SerializedLambda 的描述,注释有四大段,这里贴出并且每小段提取核心信息:

各个段落大意如下:

  • 段落一: SerializedLambda 是 Lambda 表达式的序列化形式,这类存储了 Lambda 表达式的运行时信息
  • 段落二:为了确保 Lambda 表达式的序列化实现正确性,编译器或者语言类库可以选用的一种方式是确保 writeReplace 方法返回一个 SerializedLambda 实例
  • 段落三: SerializedLambda 提供一个 readResolve 方法,其职能类似于调用"捕获类"中静态方法 $deserializeLambda$(SerializedLambda) 并且把自身实例作为入参,该过程理解为反序列化过程
  • 段落四: 序列化和反序列化产生的函数对象的身份敏感操作的标识形式(如 System.identityHashCode() 、对象锁定等等)是不可预测的

最终的结论就是:如果一个函数式接口实现了 Serializable 接口,那么它的实例就会自动生成了一个返回 SerializedLambda 实例的 writeReplace 方法,可以从 SerializedLambda 实例中获取到这个函数式接口的运行时信息。这些运行时信息就是 SerializedLambda 的属性:

属性 含义
capturingClass "捕获类",当前的 Lambda 表达式出现的所在类
functionalInterfaceClass 名称,并且以"/"分隔,返回的 Lambda 对象的静态类型
functionalInterfaceMethodName 函数式接口方法名称
functionalInterfaceMethodSignature 函数式接口方法签名(其实是参数类型和返回值类型,如果使用了泛型则是擦除后的类型)
implClass 名称,并且以&#
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JDK 1.8 引入了 Lambda 表达式,它是一种简洁而强大的编程语法,用于支持函数式编程。Lambda 表达式可以用来替代一些需要使用匿名内部类的情况,并提供了更简洁的语法来定义函数式接口的实现。 Lambda 表达式的基本语法如下: ```java (parameters) -> { body } ``` 其,parameters 是参数列表,可以为空或包含一个或多个参数,多个参数之间使用逗号分隔。body 是 Lambda 表达式的主体,可以是一个表达式或一段代码块。 下面是一些常见的 Lambda 表达式用法示例: 1. Lambda 表达式作为函数式接口的实现: ```java // 定义一个函数式接口 interface MyInterface { void doSomething(); } // 使用 Lambda 表达式实现函数式接口 MyInterface lambda = () -> { System.out.println("Doing something..."); }; // 调用 Lambda 表达式 lambda.doSomething(); ``` 2. Lambda 表达式作为参数传递给方法: ```java // 定义一个接受函数式接口作为参数的方法 void performAction(MyInterface action) { action.doSomething(); } // 使用 Lambda 表达式作为参数传递 performAction(() -> { System.out.println("Performing action..."); }); ``` 3. Lambda 表达式与集合的结合: ```java List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // 使用 Lambda 表达式遍历集合 names.forEach(name -> { System.out.println("Hello, " + name); }); ``` Lambda 表达式还支持方法引用、参数类型推断等特性,可以更进一步简化代码。它在函数式编程和并行处理等场景具有很大的优势。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值