【问题标题】:How to make function Serializable in generic way如何以通用方式使函数可序列化
【发布时间】:2018-09-18 22:51:12
【问题描述】:

我知道we can cast a function 是我们需要的Serializable

但是,我想将此转换移动到一个 generic 方法,以使使用代码不那么混乱。 我没有设法创建这样的方法。

我的具体问题是下面的地图不是Serializable

final Map<MyObject, String> map =
        new TreeMap<>(Comparator.comparing(MyObject::getCode));

我可以使用以下方法解决此问题:

final Map<MyObject, String> map =
        new TreeMap<>(Comparator.comparing((Function<MyObject, String> & Serializable) MyObject::getCode));

但我希望能够做类似的事情:

final Map<MyObject, String> map =
        new TreeMap<>(Comparator.comparing(makeSerializable(MyObject::getCode)));

public static <T, U> Function<T, U> makeSerializable(Function<T, U> function) {
    return (Function<T, U> & Serializable) function;
}

对于编译器来说这很好,但在运行时,我得到一个ClassCastException

java.lang.ClassCastException: SerializableTest$$Lambda$1/801197928 cannot be cast to java.io.Serializable

我还尝试了以下替代方法,但均未成功:

// ClassCastException
public static <T extends Serializable, U extends Serializable> Function<T, U> makeSerializable(Function<T, U> function) {
    return (Function<T, U> & Serializable) function;
}

// No ClassCastException, but NotSerializableException upon Serializing
public static <T, U> Function<T, U> makeSerializable2(Function<T, U> function) {
    return (Function<T, U> & Serializable) t -> function.apply(t);
}

是否可以创建这样的方法?

MyObject的实现:

static class MyObject implements Serializable {

    private final String code;

    MyObject(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }

}

【问题讨论】:

  • 您能否添加您的测试用例(或MCVE)以便我们测试潜在的解决方案?
  • @JacobG。他已经做到了……
  • @Eugene 在哪里?我没有看到可以序列化问题中任何内容的代码。具体来说,我想看看他是如何序列化他的对象的。
  • @JacobG。我的错 - 它在现已删除的答案中 new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(map);
  • @Eugene 哦,谢谢!我需要更快地达到 10k,这样我才能看到!

标签: java generics lambda java-8 comparator


【解决方案1】:

是的,这是可能的,只要您不使用 Function&lt;T, U&gt; 作为参数或结果的类型

相反,您可以创建自己的函数式接口,既是Function 又是Serializable

interface SerFunc<T, U> extends Function<T, U>, Serializable { }

正如@M.Prokhorov 在 cmets 中巧妙地建议的那样,您可以创建一个类似于您的方法的方法,但它接收并返回 SerFunc 的实例而不是 Function

public static <T, U> SerFunc<T, U> makeSerializable(SerFunc<T, U> function) {
    return function;
}

此方法的唯一目标是为方法引用或作为参数传递的 lambda 表达式提供 Serializable 目标类型。这就是为什么我们只是返回参数,即什么都不做。

现在您可以在代码中使用该方法,一切正常:

Map<MyObject, String> map =
    new TreeMap<>(Comparator.comparing(makeSerializable(MyObject::getCode)));

至于您的尝试失败的原因,我认为您可以在this question(@Eugene 提供的链接)的答案中找到原因,Brian Goetz 解释说这种行为是设计使然:

这是正确的,并且是设计使然。就像你不能在实例化后获取一个不可序列化的对象并使其可序列化一样,一旦创建了 lambda,它的可序列化性就会被设置。

如果 lambda 的目标类型是可序列化的(并且其捕获的参数是可序列化的),则 lambda 是可序列化的。

在您的尝试中,原始函数(由makeSerializable 方法作为参数接收)不是Serializable,因此我们创建的任何使用此不可序列化函数(实际上是捕获的参数)的 lambda 将也是不可序列化的。

【讨论】:

【解决方案2】:

好吧,如果您愿意更改您的参数(我不确定是否可以从inputFunction 中提取所需的信息,但我可以尝试挖掘一些东西),您可以动态创建一个序列化的@987654323 @,不确定这是否适合您...

public static <T, U> Function<T, U> makeSerializable(
        Class<T> targetType, // MyObject
        Class<U> returnType, // String
        String methodName) throws Throwable {

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodType methodType = MethodType.methodType(returnType, targetType);
    Function<T, U> fun = (Function<T, U>) LambdaMetafactory.altMetafactory(
            lookup,
            "apply",
            MethodType.methodType(Function.class),
            methodType,
            lookup.findVirtual(targetType, methodName, MethodType.methodType(returnType)),
            methodType,
            1) // this signals for serialization 
            .getTarget().invokeExact();
    return fun;
}

这可能需要一个布尔值并与静态方法引用一起使用...

调用将是:

Map<MyObject, String> map = new TreeMap(Comparator.comparing(makeSerializable(
            MyObject.class, String.class, "getCode")));

【讨论】:

  • 我喜欢这个答案,如果您不想像我在答案中所做的那样创建Serializable 功能界面,这是一个不错的选择。
  • 其实我不是很喜欢这个,这似乎是为我捏造的
  • @Ward 你问了这个问题,所以这完全取决于你,但是当你使用方法引用时,这就是幕后发生的事情
猜你喜欢
  • 1970-01-01
  • 2012-01-15
  • 1970-01-01
  • 1970-01-01
  • 2013-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-08
相关资源
最近更新 更多