Тип аргумента функции Java 8 stream max () Comparator vs Comparable - PullRequest
17 голосов
/ 15 апреля 2019

Я написал простой код, как показано ниже.Этот класс работает без ошибок.

public class Test {
    public static void main(String[] args) {
        List<Integer> intList = IntStream.of(1,2,3,4,5,6,7,8,9,10).boxed().collect(Collectors.toList());
        int value = intList.stream().max(Integer::compareTo).get();

        //int value = intList.stream().max(<Comparator<? super T> comparator type should pass here>).get();

        System.out.println("value :"+value);
    }
}

Как видно из комментария к коду, метод max() должен передавать аргумент типа Comparator<? super Integer>.

Но Integer::compareTo реализует Comparable интерфейс - не Comparator.

public final class Integer extends Number implements Comparable<Integer> {
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
}

Как это может работать?Метод max() говорит, что ему нужен аргумент Comparator, но он работает с аргументом Comparable.

Я знаю, что что-то неправильно понял, но теперь я знаю, что.Может кто-нибудь объяснить, пожалуйста?

Ответы [ 4 ]

19 голосов
/ 15 апреля 2019
int value = intList.stream().max(Integer::compareTo).get();

Приведенный выше фрагмент кода логически эквивалентен следующему:

int value = intList.stream().max((a, b) -> a.compareTo(b)).get();

Что также логически эквивалентно следующему:

int value = intList.stream().max(new Comparator<Integer>() {
    @Override
    public int compare(Integer a, Integer b) {
        return a.compareTo(b);
    }
}).get();

Comparator - это функциональный интерфейс, который можно использовать как лямбда или ссылку на метод, поэтому ваш код компилируется и выполняется успешно.

Я рекомендую прочитать Учебное пособие Oracle по методическим ссылкам (они используют пример сравнения двух объектов), а также Спецификацию языка Java на §15.13. Выражения справочника по методам , чтобы понять, почему это работает.

13 голосов
/ 15 апреля 2019

Я могу относиться к вашему замешательству.

У нас есть метод Comparator, который объявляет два параметра

int compare(T o1, T o2);

и у нас есть метод Integer, который принимает один параметр

int compareTo(Integer anotherInteger)

Как, черт возьми, Integer::compareTo разрешается в Comparator экземпляр?

Когда ссылка на метод указывает на метод экземпляра , анализатор может искать методы с аргументом n-1 (n - это ожидаемое количество параметров).

Вот выдержка из JLS о том, как определяются применимые методы. Я опущу первую часть о разборе выражения, предшествующего токену ::.

Во-вторых, для заданного типа функции с параметрами n идентифицируется набор потенциально применимых методов:

Если выражение ссылки на метод имеет вид ReferenceType :: [TypeArguments] Identifier, то потенциально применимыми методами являются:

  • методы-члены типа для поиска, который был бы потенциально применим (§15.12.2.1) для вызова метода, который называет Идентификатор, имеет арность n, имеет аргументы типа TypeArguments и появляется в том же классе, что и выражение ссылки на метод; плюс

  • методы-члены типа для поиска, которые потенциально могут быть применимы для вызова метода, который именует Identifier, имеет арность n-1, имеет аргументы типа TypeArguments и появляется в том же классе, что и справочное выражение метода .

Рассматриваются две разные арности, n и n-1, чтобы учесть возможность того, что эта форма ссылается либо на статический метод, либо на метод экземпляра.

...

Ссылочное выражение метода в форме ReferenceType :: [TypeArguments] Identifier можно интерпретировать по-разному. Если Identifier относится к методу экземпляра, то неявное лямбда-выражение имеет дополнительный параметр по сравнению с тем, если Identifier относится к статическому методу.

https://docs.oracle.com/javase/specs/jls/se12/html/jls-15.html#jls-15.13.1

Если бы мы должны были написать неявное лямбда-выражение из ссылки на этот метод, первый (неявный) параметр был бы экземпляром для вызова метода, а второй (явный) параметр был бы аргументом для передачи в метод.

(implicitParam, anotherInteger) -> implicitParam.compareTo(anotherInteger)

Обратите внимание, что ссылка на метод отличается от лямбда-выражения, хотя первое может быть легко преобразовано во второе. Лямбда-выражение должно быть desugared в новом методе, в то время как ссылка на метод обычно требует только загрузки соответствующего константного дескриптора метода.

Integer::compareTo реализует Comparable интерфейс - не Comparator.

Integer::compareTo как выражение не реализует никакого интерфейса. Тем не менее, он может ссылаться на / представлять различные функциональные типы, одним из которых является Comparator<Integer>.

Comparator<Integer> a = Integer::compareTo;
BiFunction<Integer, Integer, Integer> b = Integer::compareTo;
ToIntBiFunction<Integer, Integer> c = Integer::compareTo;
7 голосов
/ 15 апреля 2019

Integer реализует Comparable путем переопределения compareTo.

Это переопределение compareTo, однако, может использоваться таким образом, чтобы удовлетворить и реализовать интерфейс Comparator.

В его использовании здесь

int value = intList.stream().max(Integer::compareTo).get();

переводится на что-то вроде

int value = intList.stream().max(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
}).get();

Ссылка на метод (или лямбда-выражение) должна удовлетворять сигнатуре единственного абстрактного метода соответствующего функционального интерфейса, и в этом случае (Comparator) compareTo делает.


Идея состоит в том, что max ожидает Comparator, а его compare метод ожидает два Integer объекта. Integer::compareTo может удовлетворить эти ожидания, поскольку он также ожидает два Integer объекта. Первый - это его получатель (экземпляр, в котором должен вызываться метод), а второй - это аргумент. С новым синтаксисом Java 8 компилятор переводит один стиль в другой.

(compareTo также возвращает int в соответствии с Comparator#compare.)

1 голос
/ 16 апреля 2019

Первый трюк: все методы экземпляра фактически принимают 1 дополнительный неявный аргумент, тот, который вы называете this в теле метода. E.g.:

public final class Integer extends Number implements Comparable<Integer> {
    public int compareTo(/* Integer this, */ Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
}

Integer a = 10, b = 100;
int compareResult            = a.compareTo(b);
// this actually 'compiles' to Integer#compareTo(this = a, anotherInteger = b)

Второй трюк: Компилятор Java может «преобразовать» сигнатуру ссылки на метод в некоторый функциональный интерфейс , если число и типы аргументов (включая this) удовлетворяют:

interface MyInterface {
    int foo(Integer bar, Integer baz);
}

Integer a = 100, b = 1000;

int result1 =                   ((Comparator<Integer>) Integer::compareTo).compare(a, b);
int result2 = ((BiFunction<Integer, Integer, Integer>) Integer::compareTo).apply(a, b);
int result3 =                           ((MyInterface) Integer::compareTo).foo(a, b);

// result1 == result2 == result3

Как видите, class Integer не реализует ничего из Comparator, BiFunction или случайного MyInterface, но это не мешает вам использовать ссылку на метод Integer::compareTo в качестве этих интерфейсов.

...