如何以及为什么序列化 Lambda

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

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 63w+ 字,讲解图 2808+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2200+ 小伙伴加入学习 ,欢迎点击围观

序列化 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 对开发人员在很大程度上是透明的。这使得实现简单的分布式系统更容易使用,同时为您提供了优化其完成方式的选项。


相关文章