如何以及为什么序列化 Lambda

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

序列化 lambda 在许多用例中都很有用,例如持久化配置,或作为远程资源的 访问者模式

远程访客

例如,假设我想访问远程地图上的资源。我可以使用 get/put,但假设我只想从 Map 的值返回一个字段:我可以将 lambda 作为访问者传递以提取我想要的信息。



 MapView userMap = 
 Chassis.acquireMap("users", String.class, UserInfo.class);

userMap.put("userid", new UserInfo("User's Name"));

// print out changes userInfo.registerSubscriber(System.out::println);

// obtain just the fullName without downloading the whole object

String name= userMap.applyToKey("userid", u -> u.fullName);

// increment a counter atomically and trigger

// an updated event printed with the subscriber.

userMap.asyncUpdateKey("userid", ui -> {

 ui.usageCounter++; 

 return ui;

});

// increment a counter and return the userid

int count = userMap.syncUpdateKey("userid",

  ui -> { ui.usageCounter++; return ui;}, 

  ui -> ui.usageCounter);


如您所见,很容易添加各种简单的功能,或调用一个方法来执行您需要的操作。唯一的问题是默认情况下 lambda 是不可序列化的。

可序列化的 Lambda

使 lambda 可序列化的一种简单方法是将 & Serializable 的强制转换添加到引用 lambda 实现的变量。



 MapView userMap = 
 Chassis.acquireMap("users", String.class, UserInfo.class);

userMap.put("userid", new UserInfo("User's Name"));

// print out changes userInfo.registerSubscriber(System.out::println);

// obtain just the fullName without downloading the whole object

String name= userMap.applyToKey("userid", u -> u.fullName);

// increment a counter atomically and trigger

// an updated event printed with the subscriber.

userMap.asyncUpdateKey("userid", ui -> {

 ui.usageCounter++; 

 return ui;

});

// increment a counter and return the userid

int count = userMap.syncUpdateKey("userid",

  ui -> { ui.usageCounter++; return ui;}, 

  ui -> ui.usageCounter);


如您所见,这引入了很多样板。使用 lambda 的一个关键原因是避免样板代码,那么有什么选择呢?


在您的 API 中使 Lambda 可序列化

不幸的是,标准 API 无法更改或子类来添加它,但如果您有自己的 API,则可以使用 Serializable 接口。


 MapView userMap = 
 Chassis.acquireMap("users", String.class, UserInfo.class);

userMap.put("userid", new UserInfo("User's Name"));

// print out changes userInfo.registerSubscriber(System.out::println);

// obtain just the fullName without downloading the whole object

String name= userMap.applyToKey("userid", u -> u.fullName);

// increment a counter atomically and trigger

// an updated event printed with the subscriber.

userMap.asyncUpdateKey("userid", ui -> {

 ui.usageCounter++; 

 return ui;

});

// increment a counter and return the userid

int count = userMap.syncUpdateKey("userid",

  ui -> { ui.usageCounter++; return ui;}, 

  ui -> ui.usageCounter);


该接口可以用作参数类型。



 MapView userMap = 
 Chassis.acquireMap("users", String.class, UserInfo.class);

userMap.put("userid", new UserInfo("User's Name"));

// print out changes userInfo.registerSubscriber(System.out::println);

// obtain just the fullName without downloading the whole object

String name= userMap.applyToKey("userid", u -> u.fullName);

// increment a counter atomically and trigger

// an updated event printed with the subscriber.

userMap.asyncUpdateKey("userid", ui -> {

 ui.usageCounter++; 

 return ui;

});

// increment a counter and return the userid

int count = userMap.syncUpdateKey("userid",

  ui -> { ui.usageCounter++; return ui;}, 

  ui -> ui.usageCounter);


您的 API 的用户不必明确说明 lambda 是可序列化的。



 MapView userMap = 
 Chassis.acquireMap("users", String.class, UserInfo.class);

userMap.put("userid", new UserInfo("User's Name"));

// print out changes userInfo.registerSubscriber(System.out::println);

// obtain just the fullName without downloading the whole object

String name= userMap.applyToKey("userid", u -> u.fullName);

// increment a counter atomically and trigger

// an updated event printed with the subscriber.

userMap.asyncUpdateKey("userid", ui -> {

 ui.usageCounter++; 

 return ui;

});

// increment a counter and return the userid

int count = userMap.syncUpdateKey("userid",

  ui -> { ui.usageCounter++; return ui;}, 

  ui -> ui.usageCounter);


远程实现序列化 lambda,在服务器上执行并返回结果。


同样,也有将 lambda 应用于整个地图的方法。


查询与订阅

为了支持查询,如果您想隐式添加可序列化,则不能使用内置的 stream() API。但是,您可以创建一个尽可能相似的。



 MapView userMap = 
 Chassis.acquireMap("users", String.class, UserInfo.class);

userMap.put("userid", new UserInfo("User's Name"));

// print out changes userInfo.registerSubscriber(System.out::println);

// obtain just the fullName without downloading the whole object

String name= userMap.applyToKey("userid", u -> u.fullName);

// increment a counter atomically and trigger

// an updated event printed with the subscriber.

userMap.asyncUpdateKey("userid", ui -> {

 ui.usageCounter++; 

 return ui;

});

// increment a counter and return the userid

int count = userMap.syncUpdateKey("userid",

  ui -> { ui.usageCounter++; return ui;}, 

  ui -> ui.usageCounter);


或作为过滤订阅。



 MapView userMap = 
 Chassis.acquireMap("users", String.class, UserInfo.class);

userMap.put("userid", new UserInfo("User's Name"));

// print out changes userInfo.registerSubscriber(System.out::println);

// obtain just the fullName without downloading the whole object

String name= userMap.applyToKey("userid", u -> u.fullName);

// increment a counter atomically and trigger

// an updated event printed with the subscriber.

userMap.asyncUpdateKey("userid", ui -> {

 ui.usageCounter++; 

 return ui;

});

// increment a counter and return the userid

int count = userMap.syncUpdateKey("userid",

  ui -> { ui.usageCounter++; return ui;}, 

  ui -> ui.usageCounter);


这与常规流 API 的不同之处在于,数据可以分布在许多服务器上,并且当任何服务器上的数据发生变化时,您都会收到回调。当在服务器上应用过滤器和地图时,只有您感兴趣的数据会通过网络发送。

Java序列化

Java Serialization 是一个很好的通用的、向后兼容的序列化库。替代方案试图解决的两个最常见的问题是性能和跨平台序列化。


在上面的示例中,fullNameFunc 序列化为超过 700 个字节,并且优化它以减少消息大小或它产生的垃圾量的选项非常有限。相比之下,直接的二进制 YAML 序列化使用 348,具有更多优化序列化的选项。


这就提出了如何使用替代的、跨平台的或更快的序列化格式来序列化 lambda 的问题。

替代序列化

您可以连接到当前的序列化机制。这不受支持,它可能会随时更改,但没有其他受支持的方法可以执行此操作。


不过你可以这样做”


 MapView userMap = 
 Chassis.acquireMap("users", String.class, UserInfo.class);

userMap.put("userid", new UserInfo("User's Name"));

// print out changes userInfo.registerSubscriber(System.out::println);

// obtain just the fullName without downloading the whole object

String name= userMap.applyToKey("userid", u -> u.fullName);

// increment a counter atomically and trigger

// an updated event printed with the subscriber.

userMap.asyncUpdateKey("userid", ui -> {

 ui.usageCounter++; 

 return ui;

});

// increment a counter and return the userid

int count = userMap.syncUpdateKey("userid",

  ui -> { ui.usageCounter++; return ui;}, 

  ui -> ui.usageCounter);


这为您提供了一个对象,您可以检查该对象以提取 lambda 的内容。要么看看它调用了什么方法,要么序列化它。在反序列化方面,您可以重新创建此对象并可以对该对象进行 readResolve。


标准API

目前,没有用于自省 lambda 的标准 API。这是故意这样做的,以便将来可以更改实现,尽管没有公共 JEP 可以这样做。然而,就像作为内部 API 的 Unsafe 一样,我期待有一天我们可以使用标准 API 而不是必须深入 JVM 的内部来实现解决方案。


结论

通过对 API 进行一些更改,您可以使序列化 lambda 对开发人员在很大程度上是透明的。这使得实现简单的分布式系统更容易使用,同时为您提供了优化其完成方式的选项。