简体   繁体   中英

Generics in Java Comparator class. What is the exact type of T and U?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM