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 aComparator
? Can someone clarify all of this?
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.
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
.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.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 aStream
.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:
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 expectsComparator
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:
The lambda is initialized with its target type as Comparator and Serializable. Note the return type of method is just
Comparator
, but becauseSerializable
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 variableo
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 converto
toBar
For object, just like it would normally. For lambda, explained above.
No. Java does not have Structural Types. So no special type based on the method. It simply tries to cast
o
to bothSO1
andBar
and it fails because of latterAs 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 PredicateAs per the Java Language Specification, the cast operator (whatever is in parenthesis) can be a ReferenceType followed by one or more AdditionalBound terms, i.e., one or more interface type. Moreover, the spec also states that It is a compile-time error if the compile-time type of the operand may never be cast to the type specified by the cast operator according to the rules of casting conversion.
In your case
Foo
does not implementBar
, but this fact might not be apparent at compile time so you get aClassCastException
because although the method defined in the anonymous class has the same signature as the one defined inBar
, the object does not explicitly implementBar
. Moreover, methods defined in anonymous classes are hidden, unless called in the same statement as when they are defined, i.e.,works, but this does not: