Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java lambda expressions, casting, and Comparators

Tags:

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:

  1. How does adding an interface to a cast supposed to work? Does this just enforce the return type of an interface's method?

  2. 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?

like image 858
Joseph Nields Avatar asked Feb 13 '15 22:02

Joseph Nields


Video Answer


2 Answers

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

like image 54
Jatin Avatar answered Nov 16 '22 23:11

Jatin


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.

like image 35
Peter Lawrey Avatar answered Nov 17 '22 00:11

Peter Lawrey