it-swarm.com.ru

Java 8 потоков .min () и .max (): почему это компилируется?

Примечание: этот вопрос возник из мертвой ссылки, которая была предыдущим SO вопросом, но здесь идет ...

См. Примечание этого кода (: я знаю, что этот код не будет "работать" и что следует использовать 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 .

Итак, почему это компилируется вообще?

205
fge

Позвольте мне объяснить, что здесь происходит, потому что это не очевидно!

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

Итак, вопрос, конечно, почему Integer::max принято? Ведь это не компаратор!

Ответ заключается в том, как функционирует новая лямбда-функция в Java 8. Она опирается на концепцию, неофициально известную как интерфейсы "одного абстрактного метода" или интерфейсы "SAM". Идея состоит в том, что любой интерфейс с одним абстрактным методом может быть автоматически реализован любой лямбда - или ссылкой на метод - чья сигнатура метода совпадает с одним методом в интерфейсе. Итак, исследуем интерфейс Comparator (простая версия):

public Comparator<T> {
    T 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> в контексте метода.

240
David M. Lloyd

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());
115
Jon Skeet

Это работает, потому что Integer::min разрешает реализацию интерфейса Comparator<Integer>.

Ссылка на метод Integer::min преобразуется в Integer.min(int a, int b), разрешается в IntBinaryOperator, и, возможно, где-то происходит автобокс, делая его BinaryOperator<Integer>.

А методы min() и max()Stream<Integer> требуют реализации интерфейса Comparator<Integer>.
Теперь это разрешает единственный метод Integer compareTo(Integer o1, Integer o2). Который имеет тип BinaryOperator<Integer>.

И, таким образом, волшебство произошло, поскольку оба метода являются BinaryOperator<Integer>.

19
skiwi

Помимо информации, предоставленной Дэвидом М. Ллойдом, можно добавить, что механизм, который позволяет это, называется типизацией цели .

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

Целью выражения является переменная, которой присвоен его результат, или параметр, которому передается его результат.

Лямбда-выражениям и ссылкам на методы присваивается тип, который соответствует типу их цели, если такой тип может быть найден.

См. раздел Вывод типа в учебнике Java для получения дополнительной информации.

2
Lii

У меня была ошибка с массивом, получающим максимальное и минимальное, поэтому мое решение было:

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
0
JPRLCol