【问题标题】:Why Stream operations is duplicated with Collectors?为什么 Stream 操作与 Collectors 重复?
【发布时间】:2017-05-30 09:42:35
【问题描述】:

请允许我提出一些抱怨,也许这很无聊,但我想描述一下:“为什么会提出这个问题?”。 我昨晚回答的问题和别人hereherehere不同。

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

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

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

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

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

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

    stream.map(...).collect(collector);
    stream.collect(mapping(..., collector));
    
  4. Collector 不能像Stream 那样应用短路终端操作。例如:

    stream.filter(...).findFirst();
    

有没有人能提出其他缺点/优点,为什么 Stream 操作在这里与 Collectors 重复?我想重新理解他们。提前致谢。

【问题讨论】:

  • 因为他们就是这样设计的。如果您有投诉,请提交错误报告或改进请求,或租用大厅。
  • @Holger 感谢您的链接,因为我不擅长英语,所以我花了大部分时间来理解您的答案。
  • @EJP 先生,也许你误会了我。我所说的是我想澄清为什么会提出这个问题。抱怨不是抱怨。

标签: java java-8 java-stream


【解决方案1】:

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

另一方面,正如您自己命名的那样,Collectors 可以组合,允许在不再可能进行流操作的地方执行嵌入在另一个收集器中的这些操作。我想,这种镜像只有在 Java 8 开发的后期才变得明显,这就是为什么某些操作缺少对应的操作,比如 filteringflatMapping,它们只会在 Java 9 中出现。所以,有两个不同的 API 做类似的事情,并不是在开发之初就做出的设计决定。

【讨论】:

  • 我从来没有说过“LISP 风格”是不合理的。我只是说,Java 开发人员更习惯于链式调用风格。
  • @Holger 如果你是对的,那么考虑到当时 java-8 有多晚……
  • 我认为 jdk 不会将Stream 中的所有操作都包含在Collectors 中,因为这会使Stream 变得丑陋,而且它的责任也不清楚。
  • 收集器并不意味着取代流操作。但是在 Java 9 中,所有无状态的中间操作都可以作为收集器使用,mapping​flatMapping​filtering​(和collectingAndThen,如果你想计算的话)。同样,几乎所有非短路操作都有对应的maxByminByreducingcounting。剩下的是短路操作和forEachtoArray,它们的工作原理与收集器完全不同。
【解决方案2】:

似乎与Stream 方法重复的Collectors 方法提供了额外的功能。当与其他 Collectors 结合使用时,它们才有意义。

例如,如果我们考虑Collectors.mapping(),最常见的用法是将其传递给Collectors.groupingByCollector

考虑这个例子(取自 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&lt;City, List&lt;Person&gt;&gt;

现在,您当然可以使用people.stream().map(Person::getLastName)map Stream&lt;Person&gt; 转换为Stream&lt;String&gt;,但是您将无法通过Person 的其他属性对这些姓氏进行分组(Person::getCity 在这个例子)。

【讨论】:

  • 先生,非常感谢。我发现我已经在我的问题的第二个中描述了它。但我的太宽泛了。
猜你喜欢
  • 1970-01-01
  • 2017-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多