Why is this lambda expression cast using an ampersand?

Viewed 426

recently I stumbled over the following code in the Java Comparator class:

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

What's confusing me is the (Comparator<T> & Serializable) part. Since the method only returns a Comparator I don't see the use in casting to Serializable. I also don't see the reason to ever cast anything this way, or am I missing something?

It seems to me that if I wanted to cast an Object to multiple possible types, I could just introduce another generic type like this:

public static <T, U extends Comparable<? super U>, V extends Comparator<T> & Serializable> 
    V comparing(Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (V) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

This way the returned value could be assigned to both Comparator or Serializable variables.

The only other reason I could think of, is to use that cast as some kind of typecheck to see if the lambda expression actually returns a Serializable.

If any of you have experience with this kind of cast or an idea what casting like this could accomplish, any help would be appreciated.

3 Answers

Since the method only returns a Comparator I don't see the use in casting to Serializable.

The cast tells the type checker that the object being returned needs to implement both interfaces. This information is used when creating the object which is the result of the lambda expression.

Without the cast, the returned Comparator object implements the Comparator interface only. This is a problem if the comparator is stored in a field of a serializable object:

class MyClass implements Serializable {
     final Comparator<MyClass> comparator = Comparator.comparing(...)
}

If you now serialize an instance of this class, the comparator object gets serialized too. If the comparator object doesn't have the Serializable interface as a supertype, you will get an exception.

The ampersand defines an intersection type - You can read it as "a Class that needs to implement both the Serializable and the Comparator<T> interface".

The weird thing about this kind of cast is that it's different from all other casts in Java: If you "cast" a lambda expression like this in Java the lambda will actually implement Serializable (i.e. this doesn't just change the type the compiler knows about, but actually changes what types the object implements!)

What the cast does in this case is define a target type for the lambda expression, that it will match. The target type in this case is the intersection of Comparator<T> and Serializable, meaning the lambda instance at runtime will implement both of those interfaces.

Related