为什么许多Java Stream接口方法在参数中使用较低的有界通配符而不是泛型类型?

时间:2018-05-13 07:38:25

标签: java generics 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>为参数,T和超类型的Predicate对象可以传递给方法,但是我不能想到超类型谓词超过特定类型的情况?

例如,如果我有Stream<Integer>我可以将Predicate<Integer>, Predicate<Number>, and Predicate<Object>个对象作为其过滤方法的参数传递,但为什么有人会将Predicate<Object>传递给Predicate<Integer>

在这里使用<? super T>有什么好处?

1 个答案:

答案 0 :(得分:5)

我假设您了解PECS模式,这在设计API时非常有用,即使没有实际的用例会突然出现。当我们查看Java 8的最终状态和典型用例时,很容易认为不再需要它了。即使实际使用更通用的类型,lambda表达式和方法引用不仅会推断目标类型,而且改进的类型推断也适用于方法调用。 E.g。

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

需要使用Java 8之前的编译器进行filter(Predicate<? super T>)声明,因为它会推断Predicate<Object>表达式Predicate.isEqual("b")。但是对于Java 8,它也可以使用Predicate<T>作为参数类型,因为目标类型也用于嵌套方法调用。

我们可能会认为Stream API的开发和新的Java语言版本/编译器实现同时发生,因此可能有一个实际的理由在开始时使用PECS模式,而从来没有不使用该模式的原因。在重用现有的谓词,函数或消费者实例时,它仍然提高了灵活性,即使这可能不太常见,也不会受到伤害。

请注意,例如, Stream.of(10, 12.5).filter(n -> n.doubleValue() >= 10),因为谓词可能得到一个适合处理Stream的元素类型“#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)时才有效。 或者您为Stream强制执行不同的元素类型,

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实现传递给具有更具体元素类型的Stream的sorted方法,例如

Stream.of("FOO", "bar", "Baz")
      .sorted(Collator.getInstance())
      .forEachOrdered(System.out::println);
如果没有? super声明中的sorted(Comparator<? super T> comparator)

将无效。