如何使函数Serializable以通用方式

时间:2018-04-09 13:51:58

标签: java generics lambda java-8 comparator

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

但是,我想将此转换移动到泛型方法,以使使用代码更简洁。 我无法创建这样的方法。

我的具体问题是以下地图不是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;
    }

}

2 个答案:

答案 0 :(得分:6)

是的,它是可能的 ,只要您不使用Function<T, U>作为参数或结果

相反,您可以创建自己的FunctionSerializable功能界面:

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

正如@ M.Prokhorov在评论中巧妙地建议的那样,您可以创建一个类似于您的方法的方法,但接收并返回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都使用这个不可序列化的函数(实际上是捕获的参数)也是不可序列化的。

答案 1 :(得分:1)

好吧,如果您愿意更改参数(我不确定是否可以从input Function中提取所需信息,但我可以尝试挖掘一些内容),您可以动态创建序列化的Function,不确定这对你是否可行......

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")));