【问题标题】:Java 8 Lambda expression with Serialization带有序列化的 Java 8 Lambda 表达式
【发布时间】:2017-06-02 17:58:35
【问题描述】:

在我们的 Web 应用程序项目中,我们使用 Redis 来管理会话。为了支持它,我们正在序列化将存储在会话中的任何对象。

例如,我们使用 DTO 来保存用于在屏幕上显示的 bean 数据。即使 DTO 内部有任何其他对象(组合),我们也必须对其进行序列化,否则我们会得到 NotSerializableException

我在创建匿名内部类以实现Comparator 时遇到问题,如下所示:

Collections.sort(people, new Comparator<Person>() {
    public int compare(Person p1, Person p2) {
        return p1.getLastName().compareTo(p2.getLastName());
    }
});

上面的代码抛出了NotSerializableException,我通过创建一个实现ComparatorSerializable 接口的类来解决它。问题是,它被扔在了使用此 DTO 的 JSP 页面中。我不得不进行大量调试才能找到实际问题。

但现在,我想将上面的代码更改为使用 Lambda 表达式,如下所示:

Collections.sort(people, (p1, p2) -> p1.getLastName().compareTo(p2.getLastName()));

但是,我担心可能会发生同样的异常。 Lambda 表达式是否在内部创建对象?

【问题讨论】:

  • this 是什么意思?例如。 Comparator c = (Comparator &amp; Serializable) (p1, p2) -&gt; p1.getLastName().compareTo(p2.getLastName())
  • 对不起,我不太了解 Lambda,但是上面的代码是否意味着它会创建一个在内部实现 ComparatorSerializable 接口的对象?
  • 是的,我想是的,但到目前为止我还没有测试过。我是古玩,所以我会检查自己。

标签: java serialization lambda redis


【解决方案1】:

您可以通过

创建一个可序列化的 lambda 表达式
Collections.sort(people, (Comparator<Person>&Serializable)
    (p1, p2) -> p1.getLastName().compareTo(p2.getLastName()));

但需要注意的是,通过创建Comparator

(p1, p2) -> p1.getLastName().compareTo(p2.getLastName())

带有不鼓励的冗余。您正在调用getLastName() 两次,并且在任何一种情况下都必须注意在正确的参数变量上调用它。使用起来更直接

Comparator.comparing(Person::getLastName)

相反。你也可以使这个比较器可序列化,但这意味着失去了很多简洁性:

Collections.sort(people,
    Comparator.comparing((Function<Person,String>&Serializable)Person::getLastName));

这也更健壮。 lambda 表达式的序列化形式包含对实现方法的引用,在第一个变体中,它是一种合成方法,具有编译器生成的名称,当您在定义方法中使用另一个 lambda 表达式时,该名称可能会发生变化。相比之下,Person::getLastName 将命名方法getLastName 指向实现方法(至少与javac 相同)。

但通常,可序列化的 lambda 表达式可能包含令人惊讶的编译器依赖性,应谨慎使用。

由于它们旨在描述行为而不是数据,因此长期存储它们毫无意义。对于在具有相同代码库的 JVM 之间传输它们就足够了。

【讨论】:

    【解决方案2】:

    λ

    是的,它确实创建了一个对象。第二个参数是 Comparator 类型,所以它会被隐式创建。

    看到这个:Does a lambda expression create an object on the heap every time it's executed?


    嵌入式 Redis

    顺便说一句,允许您的应用程序在本地运行会受益匪浅。要启动嵌入式 Redis 服务器,您可以查看我在 github 上的个人项目:https://github.com/alexbt/sample-spring-boot-data-redis-embedded

    它使用以下依赖项:

    <dependency>
        <groupId>com.github.kstyrc</groupId>
        <artifactId>embedded-redis</artifactId>
        <version>0.6</version>
    </dependency>
    

    主要是这 3 行来处理它:

    new RedisServer(redisPort);
    redisServer.start();
    redisServer.stop();
    

    您甚至可以在一个辅助项目中执行此操作,只需在特定端口上停止/启动 Redis。如果您将它与特定的 Spring Profile 集成到应用程序触发器中会更好,但这在短期内会有更多的工作。


    图书馆网站:https://github.com/kstyrc/embedded-redis:

    运行 RedisServer 很简单:

    RedisServer redisServer = new RedisServer(6379);
    redisServer.start();
    // do some work
    redisServer.stop();
    

    【讨论】:

    • 尝试的问题是我们的本地机器上没有Redis环境。其次,如果它要隐式创建一个对象,那么它可能会在生产环境中抛出NotSerializableException,这对我来说非常糟糕:(
    • 我很确定它可以工作,但你的意思是它不能在生产之前进行测试??那么,如果我是你,我不会改变任何事情,除非你愿意创建一个没有外部依赖的副项目!
    • 附带说明一下,我在 github 上有这个项目,展示了使用嵌入式 Redis 服务器。从长远来看,允许您的应用程序在本地运行会受益:github.com/alexbt/sample-spring-boot-data-redis-embedded跨度>
    【解决方案3】:

    如果你愿意切换到另一个像Kryo这样的序列化框架,你可以摆脱多重边界或者实现的接口必须实现Serializable的要求。此外,您不需要让所有序列化类都实现Serializable,通常有更多选项来自定义序列化,序列化形式可能会更小(节省内存)并且整个过程可能会更快。

    lambda 序列化的方法是:

    1. 修改InnerClassLambdaMetafactory以始终生成序列化所需的代码
    2. 反序列化时直接调用LambdaMetaFactory

    详情和代码见blog post

    【讨论】:

      猜你喜欢
      • 2020-10-06
      • 1970-01-01
      • 2021-02-01
      • 2017-11-16
      • 1970-01-01
      • 2015-06-01
      • 1970-01-01
      • 2014-03-25
      • 1970-01-01
      相关资源
      最近更新 更多