I am aware that we can cast a function to be Serializable
where we need this.
However, I would like to move this casting to a generic method, to make the using code less cluttered. I do not manage to create such a method.
My specific problem is that the below map is not Serializable
:
final Map<MyObject, String> map =
new TreeMap<>(Comparator.comparing(MyObject::getCode));
I can fix this by using:
final Map<MyObject, String> map =
new TreeMap<>(Comparator.comparing((Function<MyObject, String> & Serializable) MyObject::getCode));
But I would like to be able to do something like:
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;
}
For the compiler this is fine, but at runtime, I get a ClassCastException
:
java.lang.ClassCastException: SerializableTest$$Lambda$1/801197928 cannot be cast to java.io.Serializable
I also tried the following alternatives, without success:
// 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);
}
Is it possible to create such a method?
Implementation of MyObject
:
static class MyObject implements Serializable {
private final String code;
MyObject(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
Yes, it's possible, as long as you don't use Function<T, U>
as the type of either the argument or the result.
Instead, you could create your own functional interface that is both a Function
and Serializable
:
interface SerFunc<T, U> extends Function<T, U>, Serializable { }
As @M.Prokhorov cleverly suggests in the comments, you could create a method that resembles your method, but that receives and returns an instance of SerFunc
instead of Function
:
public static <T, U> SerFunc<T, U> makeSerializable(SerFunc<T, U> function) {
return function;
}
The only goal of this method would be to provide a Serializable
target type for the method reference or lambda expression passed as an argument. This is why we're just returning the argument, i.e. doing nothing.
Now you can use the method as in your code, and everything will work fine:
Map<MyObject, String> map =
new TreeMap<>(Comparator.comparing(makeSerializable(MyObject::getCode)));
As to why your attempts failed, I think you could find the reason in an answer to this question (link provided by @Eugene), where Brian Goetz explains that this behavior is by design:
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.)
In your attempts, the original function (received by the makeSerializable
method as an argument) is not Serializable
, so any lambda we create that uses this non-serializable function (which would actually be a captured argument) will be non-serializable as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With