I was looking through Java source code for the Map
interface and ran into this little snippet of code:
/** * Returns a comparator that compares {@link Map.Entry} in natural order on value. * * <p>The returned comparator is serializable and throws {@link * NullPointerException} when comparing an entry with null values. * * @param <K> the type of the map keys * @param <V> the {@link Comparable} type of the map values * @return a comparator that compares {@link Map.Entry} in natural order on value. * @see Comparable * @since 1.8 */ public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() { return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> c1.getValue().compareTo(c2.getValue()); }
From the method declaration I get that this is a generic method that returns a Comparator of a type that is either inferred from the map entries passed to it or explicitly provided in the method.
What's really throwing me off is the return value. It appears that the lambda expression
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
is explicitly cast to a Comparator<Map.Entry<K, V>>
. Is this right?
I also noticed that the apparent cast includes & Serializable
. I've never seen an interface combined with a class in a cast before, but it looks like the following valid in the compiler:
((SubClass & AnInterface) anObject).interfaceMethod();
Although the following doesn't work:
public class Foo { public static void main(String[] args) { Object o = new Foo() { public void bar() { System.out.println("nope"); } }; ((Foo & Bar) o).bar(); } } interface Bar { public void bar(); }
So, two questions:
How does adding an interface to a cast supposed to work? Does this just enforce the return type of an interface's method?
Can you cast a Lambda expression to a Comparator
? What else can they be cast as? Or is a lambda expression essentially just a Comparator
? Can someone clarify all of this?
Though Peter has given an excellent answer, let me add more for further clarity.
A lambda get its precise type only while initialization. This is based on the target type. For example:
Comparator<Integer> comp = (Integer c1, Integer c2) -> c1.compareTo(c2); BiFunction<Integer, Integer, Integer> c = (Integer c1, Integer c2) -> c1.compareTo(c2); Comparator<Integer> compS = (Comparator<Integer> & Serializable) (Integer c1, Integer c2) -> c1.compareTo(c2);
Above its the same lambda in all 3 cases, but it gets its type based on the reference type you have provided. Hence you can set the same lambda to 3 different types on each case.
But mind you, once type is set (while initialization) then it can no longer be changed. It gets imbibed at bytecode level. So obviously you cant pass c
to a method which expects Comparator
because once initialized then they are like normal java objects. (You can look at this class to play around and generate lambdas on the go)
So in case of:
(Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> c1.getValue().compareTo(c2.getValue());
The lambda is initialized with its target type as Comparator and Serializable. Note the return type of method is just Comparator
, but because Serializable
is also inscribed to it while initialization, it can always be serialized even though this message is lost in method signature.
Now note, casting to a lambda is different from ((Foo & Bar) o).bar();
. In case of lambda, you are initializing the lambda with its type as the declared target type. But with ((Foo & Bar) o).bar();
, you are type-casting the variable o
to be of Foo and Bar. In former case, you are setting the type. In latter case, it already has a type and you are trying your luck to cast it to something else. Hence in former, it throws ClassCastException because it cant convert o
to Bar
How does adding an interface to a cast supposed to work?
For object, just like it would normally. For lambda, explained above.
Does this just enforce the return type of an interface's method?
No. Java does not have Structural Types. So no special type based on the method. It simply tries to cast o
to both SO1
and Bar
and it fails because of latter
Can you cast a Lambda expression to a Comparator? What else can they be cast as? Or is a lambda expression essentially just a Comparator? Can someone clarify all of this?
As explained above. A lambda can be initialized to any FunctionalInterface based on what all interfaces are eligible for that lambda. In above examples, you obviously cant initialize (c1, c2) -> c1.compareTo(c2)
to a Predicate
How does adding an interface to a cast supposed to work?
This has the syntax of a cast, however it is actually defining the type of the lambda you are creating via type interface. I.e. you are not creating an instance of a object which is then being cast to another type.
Does this just enforce the return type of an interface's method?
This actually defines the type the lambda will be built as at runtime. There is a LambdaMetaFactory which obtains this type at runtime and generates extra code if the type includes Serializable
.
Can you cast a Lambda expression to a Comparator?
You can only cast a reference to a type the object is already. In this case you are defining the lambda to be created must be Comparator
. You can use any type which has exactly one abstract method.
Or is a lambda expression essentially just a Comparator?
The same lambda code could be used (copy+pasted) in different contexts and different interfaces without change. It doesn't have to be a Comparator
as you will see in many other examples in the JDK.
One I find interesting is the count
method on a Stream
.
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