请允许我提出一些投诉,也许这很有意思,但我想描述一下:" 为什么会提出这个问题?"。 我昨晚回答的问题与其他here,here和here不同。
在我深入了解之后,我发现Stream和Collector之间存在许多违反Don't repeat yourself原则的重复逻辑,例如:Stream#map& Collectors#mapping,Stream#filter& jdk-9和.etc中的Collectors#filtering。
但似乎合理,因为Stream遵守Tell, Don't ask原则/ Law of Demeter和Collector遵守Composition over Inheritance原则。
我只能想到Stream操作与Collector重复的原因如下:
我们不关心如何在大背景下创建Stream。在这种情况下,Stream操作比Collector更有效,更快,因为它可以简单地将Stream映射到另一个Stream,例如:
consuming(stream.map(...));
consuming(stream.collect(mapping(...,toList())).stream());
void consuming(Stream<?> stream){...}
Collector功能更强大,可以将Collector组合在一起收集流中的元素,但Stream仅提供一些有用/高度使用的操作。例如:
stream.collect(groupingBy(
..., mapping(
..., collectingAndThen(reducing(...), ...)
)
));
Stream操作比Collector更具表现力,但它们比Collector更慢,因为它将为每个操作创建一个新流并且Stream比Collector更重,更抽象。例如:
stream.map(...).collect(collector);
stream.collect(mapping(..., collector));
Collector无法将短路终端操作应用为Stream。例如:
stream.filter(...).findFirst();
有没有人能够提出其他不利/优势,为什么Stream操作与收藏家重复?我想重新理解它们。提前谢谢。
答案 0 :(得分:7)
链接一个专用的终端流操作可能会被那些用于链接方法调用而不是组合的收集器工厂调用的“LISP样式”更具表现力。但它也允许流实现的优化执行策略,因为它知道实际操作而不仅仅是看到Collector
抽象。
另一方面,正如您自己命名的那样,Collector
可以被组合,允许在不再可能进行流操作的地方执行嵌入在另一个收集器中的这些操作。我想,这种镜像只有在Java 8开发的后期才会变得明显,这就是为什么有些操作缺少对应的原因,比如filtering
或flatMapping
,这些只会在Java 9中出现。因此,让两个不同的API做类似的事情,并不是在开发开始时做出的设计决定。
答案 1 :(得分:6)
似乎重复Collectors
方法的Stream
方法提供了额外的功能。当与其他Collector
组合使用时,它们是有意义的。
例如,如果我们考虑Collectors.mapping()
,最常见的用途是将其传递给Collectors.groupingBy
Collector
。
考虑这个例子(取自Javadoc):
List<Person> people = ...
Map<City, Set<String>> namesByCity
= people.stream().collect(groupingBy(Person::getCity, TreeMap::new,
mapping(Person::getLastName, toSet())));
此处使用 mapping
将每个组的值Collection
的元素类型从Person
转换为String
。
没有它(以及toSet()
Collector
),输出将为Map<City, List<Person>>
。
现在,您当然可以使用map
Stream<Person>
Stream<String>
到people.stream().map(Person::getLastName)
,但是您将无法通过其他属性对这些姓氏进行分组。 Person
(此示例中为Person::getCity
)。