Почему многие методы интерфейса Java Stream используют подстановочный знак в параметрах вместо ограниченного типа? - PullRequest
0 голосов
/ 13 мая 2018

Многие методы интерфейса Java Stream используют в параметрах подстановочный знак с ограничением снизу

например

Stream<T> filter(Predicate<? super T> pred)

и

void forEach(Consumer<? super T> action)

В чем преимущество использования Predicate<? super T> над Predicate<T> здесь?

Я понимаю, с Predicate<? super T> в качестве параметра, объект предиката T и супертипов может быть передан в метод, но я не могу вспомнить ситуацию, когда предикат супертипа необходим для определенного типа?

Например, если у меня есть Stream<Integer>, я могу передать Predicate<Integer>, Predicate<Number>, and Predicate<Object> объекты в качестве аргументов его методу фильтра, но зачем кому-то передавать Predicate<Object> через Predicate<Integer>?

В чем преимущество использования <? super T> здесь?

1 Ответ

0 голосов
/ 14 мая 2018

Я предполагаю, что вы знаете о шаблоне PECS , который полезно придерживаться при разработке API, даже если ни один фактический вариант использования не бросается вам в глаза.Когда мы смотрим на конечное состояние Java 8 и типичные варианты использования, возникает соблазн думать, что это больше не нужно.Лямбда-выражения и ссылки на методы не только выводят целевой тип, даже когда фактически используется более общий тип, но и улучшенный вывод типа применяется и к вызовам методов.Например,

Stream.of("a", "b", "c").filter(Predicate.isEqual("b"));

потребует объявления filter(Predicate<? super T>) с компилятором до Java 8, поскольку для выражения Predicate.isEqual("b") будет выведено Predicate<Object>.Но в Java 8 он также будет работать с Predicate<T> в качестве типа параметра, поскольку целевой тип также используется для вызова вложенных методов.

Мы можем считать, что разработка Stream API и новой JavaРеализация языковой версии / компилятора произошла в одно и то же время, поэтому могла существовать практическая причина использовать шаблон PECS в начале, хотя никогда не было причины не использовать этот шаблон.Это все еще повышает гибкость, когда дело доходит до повторного использования существующих экземпляров предикатов, функций или потребителей, и даже если это не так часто встречается, это не повредит.

Обратите внимание, что хотя, например, Stream.of(10, 12.5).filter(n -> n.doubleValue() >= 10), работает,поскольку предикат может получить предполагаемый неденазванный тип, подходящий для обработки типа элемента потока «#1 extends Number & Comparable<#1>», вы не можете объявить переменную этого типа.Если вы хотите сохранить предикат в переменной, вы должны использовать, например,

Predicate<Number> name = n -> n.doubleValue()>=10;
Stream.of(10, 12.5).filter(name);

, который работает, только если filter был объявлен как filter(Predicate<? super T> predicate).Или вы применяете другой тип элемента для потока,

Predicate<Number> name = n -> n.doubleValue()>=10;
Stream.<Number>of(10, 12.5).filter(name);

, который уже демонстрирует, как пропуск ? super в объявлении filter может вызвать больше многословия на стороне вызывающего.Кроме того, применение более общего типа элемента может быть невозможным, если на более поздней стадии конвейера требуется более конкретный тип.

Хотя существующие реализации функций встречаются редко, они есть, например

Stream.Builder<Number> b = Stream.builder();
IntStream.range(0, 10).boxed().forEach(b);
LongStream.range(0, 10).boxed().forEach(b);
Stream<Number> s = b.build();

не будет работать без ? super в объявлении forEach(Consumer<? super T> action).

Случай, с которым вы можете столкнуться чаще, это наличие существующей реализации Comparator, которую вы, возможно, захотите передатьsorted метод потока с более конкретным типом элемента, например,

Stream.of("FOO", "bar", "Baz")
      .sorted(Collator.getInstance())
      .forEachOrdered(System.out::println);

не будет работать без ? super в объявлении sorted(Comparator<? super T> comparator).

...