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

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

например

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

а также

void forEach(Consumer<? super T> action)

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

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

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

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

2 ответа

Решение

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

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

потребует filter(Predicate<? super T>) объявление с компилятором до Java 8, как это будет выводить Predicate<Object> для выражения Predicate.isEqual("b"), Но с 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) декларация.

Самое простое объяснение:

stream.filter(Objects::nonNull)

Objects::nonNull это Predicate<Object>, Если filter из Stream<T>принял бы только Predicate<T>Вы не сможете пройти мимо.

Другой пример:

stream.mapToInt(Number::intValue)

Это работает для любых числовых типов. Если вы измените свой поток из Stream<Integer> в Stream<AtomicInteger> или же Stream<Long>вам не нужно менять mapToInt(Number::intValue), Это в основном "программирование на (самый общий) интерфейс".

Другие вопросы по тегам