为什么Stream操作与收集器重复?

时间:2017-05-30 09:42:35

标签: java java-8 java-stream

请允许我提出一些投诉,也许这很有意思,但我想描述一下:" 为什么会提出这个问题?"。 我昨晚回答的问题与其他hereherehere不同。

在我深入了解之后,我发现StreamCollector之间存在许多违反Don't repeat yourself原则的重复逻辑,例如:Stream#map& Collectors#mappingStream#filter& jdk-9和.etc中的Collectors#filtering

但似乎合理,因为Stream遵守Tell, Don't ask原则/ Law of DemeterCollector遵守Composition over Inheritance原则。

我只能想到Stream操作与Collector重复的原因如下:

  1. 我们不关心如何在大背景下创建Stream。在这种情况下,Stream操作比Collector更有效,更快,因为它可以简单地将Stream映射到另一个Stream,例如:

    consuming(stream.map(...));
    consuming(stream.collect(mapping(...,toList())).stream());
    
    void consuming(Stream<?> stream){...}
    
  2. Collector功能更强大,可以将Collector组合在一起收集流中的元素,但Stream仅提供一些有用/高度使用的操作。例如:

    stream.collect(groupingBy(
      ..., mapping(
            ..., collectingAndThen(reducing(...), ...)
           )
    ));
    
  3. 在执行更简单的工作时,
  4. Stream操作比Collector更具表现力,但它们比Collector更慢,因为它将为每个操作创建一个新流并且StreamCollector更重,更抽象。例如:

    stream.map(...).collect(collector);
    stream.collect(mapping(..., collector));
    
  5. Collector无法将短路终端操作应用为Stream。例如:

    stream.filter(...).findFirst();
    
  6. 有没有人能够提出其他不利/优势,为什么Stream操作与收藏家重复?我想重新理解它们。提前谢谢。

2 个答案:

答案 0 :(得分:7)

链接一个专用的终端流操作可能会被那些用于链接方法调用而不是组合的收集器工厂调用的“LISP样式”更具表现力。但它也允许流实现的优化执行策略,因为它知道实际操作而不仅仅是看到Collector抽象。

另一方面,正如您自己命名的那样,Collector可以被组合,允许在不再可能进行流操作的地方执行嵌入在另一个收集器中的这些操作。我想,这种镜像只有在Java 8开发的后期才会变得明显,这就是为什么有些操作缺少对应的原因,比如filteringflatMapping,这些只会在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)。