2

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

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>, 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
Sweeper
  • 213,210
  • 22
  • 193
  • 313
Biliking
  • 136
  • 1
  • 10
  • "*why does this NOT compile?* `Function f22 = GamingComputer::getProperty;`" this LHS says it can take any Computer. It cannot. The RHS says it must take a GamingComputer – Michael Sep 09 '20 at 02:19
  • @Michael Then it brings me to my previous question, why does c22 compile? c22 uses my modded comparingT() method, which has a fixated T across the method. However, we can clearly see that c22 consists of both Computer and GamingComputer. – Biliking Sep 09 '20 at 02:31
  • Please don't write two questions in one post. I have edited to only include the question with more details. – Sweeper Sep 09 '20 at 03:12

1 Answers1

1

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 on their parameters and ? 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?

Sweeper
  • 213,210
  • 22
  • 193
  • 313