Примечание: этот вопрос исходит из мертвой ссылки, которая была предыдущим вопросом SO, но здесь идет...
Смотрите этот код (note: Я знаю, что этот код не будет работать и что Integer::compare
должен использоваться - я просто извлек его из связанного вопроса):
final ArrayList <Integer> list
= IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());
System.out.println(list.stream().max(Integer::max).get());
System.out.println(list.stream().min(Integer::min).get());
В соответствии с javadoc .min()
и .max()
, аргумент обоих должен быть Comparator
. Однако здесь ссылки на методы относятся к статическим методам класса Integer
.
Итак, почему эта компиляция вообще?
Ответ 1
Позвольте мне объяснить, что здесь происходит, потому что это не очевидно!
Во-первых, Stream.max()
принимает экземпляр Comparator
, чтобы элементы в потоке могли сравниваться друг с другом, чтобы найти минимум или максимум в каком-то оптимальном порядке, о котором вам не нужно слишком беспокоиться.
Итак, вопрос, конечно, в том, почему принят Integer::max
? Ведь это не компаратор!
Ответ заключается в том, что новая функциональность лямбды работает в Java 8. Она опирается на концепцию, которая неофициально известна как интерфейсы с одним абстрактным методом или интерфейсы SAM. Идея состоит в том, что любой интерфейс с одним абстрактным методом может быть автоматически реализован с помощью любой привязки лямбда - или метода, чья сигнатура метода соответствует одному методу интерфейса. Поэтому рассмотрим интерфейс Comparator
(простая версия):
public Comparator<T> {
int compare(T o1, T o2);
}
Если метод ищет Comparator<Integer>
, то он в основном ищет эту подпись:
int xxx(Integer o1, Integer o2);
Я использую "xxx" , потому что имя метода не используется для целей сопоставления.
Следовательно, как Integer.min(int a, int b)
, так и Integer.max(int a, int b)
достаточно близки, чтобы автобоксинг позволял отображать это как Comparator<Integer>
в контексте метода.
Ответ 2
Comparator
- это функциональный интерфейс, а Integer::max
соответствует этому интерфейсу (после учета автобоксинга/распаковки). Он принимает два значения int
и возвращает int
- так же, как вы ожидали бы Comparator<Integer>
(опять же, прищурившись, чтобы игнорировать разницу в Integer/int).
Однако я не ожидал, что он поступит правильно, учитывая, что Integer.max
не соответствует семантике Comparator.compare
. И действительно, это вообще не работает. Например, сделайте одно небольшое изменение:
for (int i = 1; i <= 20; i++)
list.add(-i);
... и теперь значение max
равно -20, а значение min
равно -1.
Вместо этого оба вызова должны использовать Integer::compare
:
System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());
Ответ 3
Это работает, потому что Integer::min
разрешает реализацию интерфейса Comparator<Integer>
.
Ссылка метода Integer::min
разрешается до Integer.min(int a, int b)
, разрешена к IntBinaryOperator
, и предположительно автобоксинг происходит где-то, делая его BinaryOperator<Integer>
.
И методы min()
resp max()
Stream<Integer>
задают интерфейс Comparator<Integer>
, который будет реализован.
Теперь это разрешает единственный метод Integer compareTo(Integer o1, Integer o2)
. Который имеет тип BinaryOperator<Integer>
.
Таким образом, волшебство произошло, поскольку оба метода - это BinaryOperator<Integer>
.
Ответ 4
Помимо информации, предоставленной Дэвидом М. Ллойдом, можно добавить, что механизм, который позволяет это, называется целевым типом.
Идея заключается в том, что тип, который компилятор присваивает лямбда-выражениям или ссылкам метода, не зависит только от самого выражения, но также от того, где он используется.
Цель выражения - это переменная, которой назначен ее результат, или параметр, которому передается его результат.
Лямбда-выражениям и методам присваивается тип, который соответствует типу их цели, если такой тип можно найти.
Дополнительную информацию см. в разделе ввода типов в учебнике Java.