Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make function Serializable in generic way

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;
    }

}
like image 852
Ward Avatar asked Jan 28 '23 08:01

Ward


1 Answers

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.

like image 176
fps Avatar answered Jan 30 '23 21:01

fps