Generics в классе Java Comparator. Какой точный тип T и U?

Sep 09 2020

Рассмотрим два следующих метода. Их единственное отличие заключается в объявлении общего типа 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));
}

Допустим, у меня есть List<GamingComputer> = { ASUS, MSI }, где GamingComputerпростирается Computer. Теперь я хочу их отсортировать.

List.sort( comparing( Computer::getProperty ) )

Какой тип Т?

Моя интуиция: T=GamingComputer. comparing()принимает keyExtractor, тип которого Function<Computer super GamingComputer, Property>. Наконец-то comparing()возвращается Comparator<GamingComputer>.

Этот код, доказывая мою интуицию, отлично компилируется:

Function<Computer, Property> f1 = Computer::getProperty;
Comparator<GamingComputer> c1 = comparing(f1);

Теперь, Печ, так как c1, c2добавляются в коллекцию / конструктор / метод, до тех пор , как коллекция обрабатывает их родительский класс, он может обрабатывать любой класс ребенка. Это причина <? super T>.

Как показано в этом коде:

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

Поскольку f2работает со всеми Computer, он должен уметь работать и с любыми GamingComputer. Однако, поскольку мы не объявили тип as <? super T>, мы не можем построить объект Comparatorof GamingComputers.

Имеет смысл. Потом...

Comparator<GamingComputer> c22 = comparingT(Computer::getProperty); // compiles successfuly... WT, excuse mi French, heck???

Мое предположение: comparingT()with type вызывает T=GamingComputerуныние keyExtractor, которое есть Computer::getProperty. Это заставляет всех Computersиспользовать GamingComputer::getProperty, что, вероятно, не является проблемой, поскольку Comparator<GamingComputer>сравнивает GamingComputers.

Но почему это НЕ компилируется?

Function<Computer, Property> f22 = GamingComputer::getProperty;

Ошибка очень своеобразная:

На нестатический метод нельзя ссылаться из статического контекста, что, вероятно, является ошибкой Intellij.

На нестатический метод нельзя ссылаться из статического контекста в потоках java 8

Тем не менее, при компиляции:

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

Ответы

1 Sweeper Sep 09 2020 at 09:48

Моя интуиция: T=GamingComputer

Ваша интуиция верна.


Почему Comparator<GamingComputer> c22 = comparingT(Computer::getProperty);компилируется?

Это потому, что в отличие от экземпляров функциональных интерфейсов, которые инвариантны, ссылки на методы ковариантны и контравариантны. Используя пример отсюда , вы можете сделать что-то вроде:

// in SomeClass
public static Integer function(Object o) {
    return 2;
}

// ...
Function<String, Object> function = SomeCLass::function;

Или используя свои классы, вы можете:

Function<GamingComputer, Property> f = Computer::getProperty;

Это «как будто» ссылки ? superна методы имеют свои параметры и ? extendsвозвращаемые типы! Детали того, что работает, а что нет, указаны в разделе 15.13.2 Спецификации языка Java.

Так c22, Tдо сих пор GamingComputer. Ссылку на метод Computer::getPropertyможно преобразовать в Function<GamingComputer, Property>ссылку на метод.

Это не компилируется, хотя f2«хранит» Computer::getProperty:

Comparator<GamingComputer> c2 = comparingT(f2);

потому что f2это не ссылка на метод. Это переменная.

Почему Function<Computer, Property> f22 = GamingComputer::getProperty;не компилируется?

f22сможет принять любой вид Computer, так как он принимает Computer. Если вы дадите f22другой компьютер (нет GamingComputer), то GamingComputer.getPropertyуж точно не справитесь с этим, не так ли?