Consider the two following methods. Their only difference is in their generic type declaration of Function<>
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));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparingT(
Function<T, ? extends U> keyExtractor) <-- Check here! T instead of ? super T
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
Let's say I have a List<GamingComputer> = { ASUS, MSI }
, where GamingComputer
extends Computer
. Now, I want to sort them.
List.sort( comparing( Computer::getProperty ) )
What is the type of T?
My intuition: T=GamingComputer
. comparing()
takes in keyExtractor
, whose type is Function<Computer super GamingComputer, Property>
. Finally, comparing()
returns Comparator<GamingComputer>
.
This code, proving my intuition, compiles perfectly:
Function<Computer, Property> f1 = Computer::getProperty;
Comparator<GamingComputer> c1 = comparing(f1);
Now, by PECS, since c1
, c2
are being added to a collection/ constructor/ method, as long as the collection handles their parent class, it could handle any child class. That's the reason behind <? super T>
<? super T>
.
As demonstrated in this code:
Function<Computer, Property> f2 = Computer::getProperty;
Comparator<GamingComputer> c2 = comparingT(f2); // FAILS to compile. Required Type: Comparator<GamingComputer>, Provided Comparator<Computer>
Comparator<Computer> c2 = comparingT(f2); // compiles successfuly
Since f2
works with all Computer
, it should be able to work with any GamingComputer
as well. However, because we did not declare type as <? super T>
<? super T>
, we are unable to construct a Comparator
of GamingComputers
.
Makes sense. Then...
Comparator<GamingComputer> c22 = comparingT(Computer::getProperty); // compiles successfuly... WT, excuse mi French, heck???
My guess: comparingT()
with type T=GamingComputer
forces a downcast on keyExtractor
, which is Computer::getProperty
. It forces all Computers
to use GamingComputer::getProperty
, which is probably not an issue, since Comparator<GamingComputer>
does compare GamingComputers
.
But, why does this NOT compile?
Function<Computer, Property> f22 = GamingComputer::getProperty;
The error is very peculiar:
Non-static method cannot be referenced from a static context, which is probably a bug from Intellij
Non-static method cannot be referenced from a static context in java 8 streams
Still, when compiling:
java: incompatible types: invalid method reference
method getPart in class function.GamingComputer cannot be applied to given types
required: no arguments
found: function.Computer
reason: actual and formal argument lists differ in length
My intuition:
T=GamingComputer
Your intuition is correct.
Why does
Comparator<GamingComputer> c22 = comparingT(Computer::getProperty);
compile?
This is because unlike instances of functional interfaces which are invariant, method references are covariant and contravariant. Using an example from here , you can do something like:
// in SomeClass
public static Integer function(Object o) {
return 2;
}
// ...
Function<String, Object> function = SomeCLass::function;
Or using your classes, you can do:
Function<GamingComputer, Property> f = Computer::getProperty;
It's "as if" method references have ? super
? super
on their parameters and ? extends
? extends
on the return types! The details of what works and what doesn't are specified in section 15.13.2 of the Java Language Specification.
So for c22
, T
is still GamingComputer
. The method reference Computer::getProperty
can be converted to Function<GamingComputer, Property>
it's a method reference.
This does not compile, even though f2
"stores" Computer::getProperty
:
Comparator<GamingComputer> c2 = comparingT(f2);
because f2
is not a method reference itself. It is a variable.
Why does
Function<Computer, Property> f22 = GamingComputer::getProperty;
not compile?
f22
would be able to accept any kind of Computer
, since it accepts Computer
. If you give f22
another kind of computer (not GamingComputer
), GamingComputer.getProperty
certainly would not be able to handle that, would it?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.