【问题标题】:What's the best way of ensuring a Function argument is serializable?确保 Function 参数可序列化的最佳方法是什么?
【发布时间】:2015-09-15 21:02:13
【问题描述】:

我正在编写一个带有多个参数的可序列化类,包括Function

public class Cls implements Serializable {
    private final Collection<String> _coll;
    private final Function<String, ?> _func;

    public Cls(Collection<String> coll, Function<String, ?> func) {
        _coll = coll;
        _func = func;        
    }
}

func 存储在成员变量中,因此需要可序列化。 Java lambda are serializable if the type they're being assigned to is serializable。确保在构造函数中传递的Function 是可序列化的(如果它是使用 lambda 创建的)的最佳方法是什么?

  1. 创建一个SerializableFunction 类型并使用它:

    public interface SerializableFunction<F, R> implements Function<F, R>, Serializable {}
    ....
    public Cls(Collection<String> coll, SerializableFunction<String, ?> func) {...}
    

    问题:

    • 现在collfunc 参数之间不匹配,因为func 在签名中被声明为可序列化,但coll 不是,但两者都需要可序列化才能工作。
    • 它不允许其他可序列化的Function 实现。
  2. 在构造函数上使用类型参数:

    public <F extends Function<String, ?> & Serializable>
    Cls(Collection<String> coll, F func) {...}
    

    问题:

    • 比 1 更灵活,但更令人困惑。
    • 两个参数之间仍然不匹配 - func 参数需要在编译时类型层次结构中实现 Serializable,但 coll 只需要可序列化以某种方式 (尽管如果需要,可以放弃此要求)。

    编辑在尝试使用 lambda 或方法引用调用时,此代码实际上并未编译。

  3. 留给调用者处理

    这要求调用者(从 javadocs 或反复试验)知道参数需要可序列化,并根据需要进行强制转换:

    Cls c = new Cls(strList, (Function<String, ?> & Serializable)s -> ...);
    

    Cls c = new Cls(strList, (Function<String, ?> & Serializable)Foo::processStr);
    

    这是丑陋的 IMO,使用 lambda 的初始幼稚实现保证会被破坏,而不是像 coll 那样工作(因为大多数集合以某种方式可序列化)。这也将类的实现细节推送给调用者。

目前我倾向于选项 2,因为它对调用者的负担最小,但我认为这里没有理想的解决方案。有关如何正确执行此操作的任何其他建议?

编辑:可能需要一些背景知识。这是一个在storm 内部运行的类,在一个bolt 中,它被序列化以转移到一个remove 集群来执行。该函数在集群上运行时正在对已处理的元组执行操作。因此,类的可序列化和函数参数可序列化是该类目的的很大一部分。如果不是,则该类根本不可用。

【问题讨论】:

  • 为什么要删除第二个选项?您只需将public 修饰符放在正确的位置,即类型参数的声明之前。 public &lt;F extends Function&lt;String, ?&gt; &amp; Serializable&gt; Cls(Collection&lt;String&gt; coll, F func) { …
  • @thecoop:我猜,您是在谈论传递 lambda 表达式的尝试,因为它适用于实现两个接口的具体类型。因此,如果编译器无法推断 lambda 表达式的类型参数,则必须插入显式类型转换(或提供类型参数)。那么它并不比选项 3 更简洁,但它仍然执行约束,这就是你的问题的全部内容。
  • Java 泛型在变得复杂和丑陋之前已经走了这么远。如果您想要 FP 质量(以代码表示),请使用功能更强大的 JVM 语言,例如 Scala。尽管 Scala 有自己的问题。一开始,java 泛型 + lambdas 让人想起 C++ 模板:非常好,不稳定,并且要适度使用。过度使用它不会有效地发展。我的意思是:最好不要指定最大值。
  • 使用 Java 的主要原因之一是类型安全。鉴于您正在做的事情将类型安全抛到了窗外,我认为这应该被视为您的设计存在根本性错误的暗示。也就是说,不要尝试序列化函数,而只是序列化数据并使用类来存储函数。
  • 不幸的是,序列化将所有规则抛到了窗外。它是一种伪装成库功能的语言功能。它是一种伪装成静态类型特性的动态类型特性。它违反了 OO 的所有规则(对象不再由构造函数专门创建)。所以,尽管像@bhspencer 这样的人会想对你摇摆手指,一旦你使用序列化,你已经处于一个严重妥协的世界,你只能选择最不坏的替代方案在你面前。

标签: java lambda java-8 serializable


【解决方案1】:

在大多数情况下,答案是:不要

您可能会注意到 JRE 的大多数类,甚至 ObjectOutputStream.writeObject 都不会在其签名中强制执行 Serializable。有太多的 API 不是专门针对序列化的,其中有关实现 Serializable 的对象的编译时信息会丢失,并且如果后者将其输入强制为 Serializable,则将它们与序列化一起使用将需要大量类型转换。

由于您的参数之一是 Collection,您可以从该 API 获得示例:

Collections.unmodifiableList:

如果指定的列表是可序列化的,则返回的列表将是可序列化的。

您会发现更多这样的操作,它们关心保留序列化功能而不保留结果上的Serializable 编译时类型。

这也适用于所有非public 类型,例如Collections.emptyList()Arrays.asList(…)Comparator.reverseOrder() 的结果。他们都是Serializable而不声明它。


此外,除了序列化之外,每个具有更多用例的类都应该避免强制始终为Serializable。这将阻碍不涉及序列化的使用。

关于Collection 参数,您可以考虑完全删除可序列化约束。通常,您会保护您的班级免受以后对您收到的集合的更改。一个简单的解决方案是复制集合,当您这样做时,您可以使用支持序列化的类型。

即使您想避免复制,序列化本身也是一个复制过程,因此您可以简单地创建自定义readObjectwriteObject 方法来存储Collection内容 ,无需拥有Serializable 集合。


总而言之,通常的策略是如果您的类的用户打算序列化它的实例,则用户有责任将放入其中的所有组件都是Serializable

【讨论】:

  • 添加了更多关于为什么需要序列化的信息
猜你喜欢
  • 1970-01-01
  • 2010-09-10
  • 2019-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-30
  • 2010-11-06
  • 2010-11-06
相关资源
最近更新 更多