如何以通用方式使函数可序列化
How to make function Serializable in generic way
我知道 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/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;
}
}
是的,这是可能的,只要您不使用 Function<T, U>
作为参数或结果的类型.
相反,您可以创建自己的功能界面,它既是 Function
又是 Serializable
:
interface SerFunc<T, U> extends Function<T, U>, Serializable { }
正如@M.Prokhorov 在评论中巧妙地建议的那样,您可以创建一个类似于您的方法的方法,但它接收 returns 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 提供的 link)的答案中找到原因,其中 Brian Goetz 解释说此行为是设计使然:
This is correct, and by design. Just as you cannot take a non-serializable object and make it serializable after instantiation, once a lambda is created, its serializability is set.
A lambda is serializable if its target type is serializable (and its captured arguments are serializable.)
在您的尝试中,原始函数(由 makeSerializable
方法作为参数接收)不是 Serializable
,因此我们创建的任何使用此 non-serializable 函数的 lambda(它实际上是一个捕获的参数)也将是 non-serializable。
好吧,如果你愿意改变你的参数(我不确定是否可以从 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")));
我知道 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/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;
}
}
是的,这是可能的,只要您不使用 Function<T, U>
作为参数或结果的类型.
相反,您可以创建自己的功能界面,它既是 Function
又是 Serializable
:
interface SerFunc<T, U> extends Function<T, U>, Serializable { }
正如@M.Prokhorov 在评论中巧妙地建议的那样,您可以创建一个类似于您的方法的方法,但它接收 returns 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 提供的 link)的答案中找到原因,其中 Brian Goetz 解释说此行为是设计使然:
This is correct, and by design. Just as you cannot take a non-serializable object and make it serializable after instantiation, once a lambda is created, its serializability is set.
A lambda is serializable if its target type is serializable (and its captured arguments are serializable.)
在您的尝试中,原始函数(由 makeSerializable
方法作为参数接收)不是 Serializable
,因此我们创建的任何使用此 non-serializable 函数的 lambda(它实际上是一个捕获的参数)也将是 non-serializable。
好吧,如果你愿意改变你的参数(我不确定是否可以从 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")));