3

Please allow me to make some complaints, maybe it is boringly but I want to describe:"Why did this question will be raised?". I have answered questions is different from others here, here and here last night.

After I get dig into it, I found there are many duplicated logic between Stream and Collector that violates Don't repeat yourself principle, e.g: Stream#map & Collectors#mapping, Stream#filter & Collectors#filtering in jdk-9 and .etc.

But it seems to reasonable since Stream abide by Tell, Don't ask principle/Law of Demeter and Collector abide by Composition over Inheritance principle.

I can only think of a few reasons why Stream operations is duplicated with Collectors as below:

  1. We don't care of how the Stream is created in a big context. in this case Stream operation is more effectively and faster than Collector since it can mapping a Stream to another Stream simply, for example:

    consuming(stream.map(...));
    consuming(stream.collect(mapping(...,toList())).stream());
    
    void consuming(Stream<?> stream){...}
    
  2. Collector is more powerful that can composes Collectors together to collecting elements in a stream, However, Stream only providing some useful/highly used operations. for example:

    stream.collect(groupingBy(
      ..., mapping(
            ..., collectingAndThen(reducing(...), ...)
           )
    ));
    
  3. Stream operations is more expressiveness than Collector when doing some simpler working, but they are more slower than Collectors since it will creates a new stream for each operation and Stream is more heavier and abstract than Collector. for example:

    stream.map(...).collect(collector);
    stream.collect(mapping(..., collector));
    
  4. Collector can't applying short-circuiting terminal operation as Stream. for example:

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

Does anyone can come up with other disadvantage/advantage why Stream operations is duplicated with Collectors here? I'd like to re-understand them. Thanks in advance.

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
holi-java
  • 29,655
  • 7
  • 72
  • 83
  • Because that's the way they designed it. If you have a complaint, submit a bug report or a request for enhancement, or hire a hall. – user207421 May 30 '17 at 10:00
  • 3
    See also [Collectors.summingInt() vs mapToInt().sum()](https://stackoverflow.com/q/37023822/2711488) – Holger May 30 '17 at 10:02
  • @Holger thanks for the link, I cost most of time to understand answer of yours since I'm not good at english. – holi-java May 30 '17 at 10:05
  • @EJP sir, maybe you misunderstand me. what I have said is I want to clarify why this question is raised. complaints is not gripe. – holi-java May 30 '17 at 10:21

2 Answers2

8

Chaining a dedicated terminal stream operation might be considered more expressive by those being used to chained method calls rather than the “LISP style” of composed collector factory calls. But it also allows optimized execution strategies for the stream implementation, as it knows the actual operation instead of just seeing a Collector abstraction.

On the other hand, as you named it yourself, Collectors can be composed, allowing to perform these operation embedded in another collector, at places where stream operations are not possible anymore. I suppose, this mirroring become apparent only at a late stage of the Java 8 development, which is the reason why some operations lacked their counterpart, like filtering or flatMapping, which will be there only in Java 9. So, having two different APIs doing similar things, was not a design decision made at the start of the development.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 2
    I never said that the “LISP style” was unreasonable. All I said, is, that Java developers are more used to the chained invocation style. – Holger May 30 '17 at 10:54
  • 1
    @Holger if you are right then this makes perfect sense, considering how late java-8 was at that time... – Eugene May 30 '17 at 11:12
  • I don't think jdk will includes all operations in `Stream` as `Collector`s since it will makes `Stream` ugly and its responsibility is unclear. – holi-java May 30 '17 at 11:18
  • 3
    Collectors are not meant to supersede stream operations. But with Java 9, all stateless intermediate operations will be available as collector, `mapping​`, `flatMapping​`, `filtering​`, (and `collectingAndThen`, if you want to count that). Likewise, almost all non-short-circuiting operation have a counterpart, `maxBy`, `minBy`, `reducing`, `counting`. What’s left, are short-circuiting operations and `forEach` and `toArray`, which work fundamentally different to collectors. – Holger May 30 '17 at 11:33
7

The Collectors methods that seem to duplicate Stream methods are offering additional functionality. They make sense when used in combination with other Collectors.

For example, if we consider Collectors.mapping(), the most common use is to pass it to a Collectors.groupingBy Collector.

Consider this example (taken from the Javadoc):

List<Person> people = ...
Map<City, Set<String>> namesByCity
         = people.stream().collect(groupingBy(Person::getCity, TreeMap::new,
                                              mapping(Person::getLastName, toSet())));

mapping is used here to transform the element type of the value Collection of each group from Person to String.

Without it (and the toSet() Collector) the output would be Map<City, List<Person>>.

Now, you can certainly map a Stream<Person> to a Stream<String> using people.stream().map(Person::getLastName), but then you will lose the ability to group these last names by some other property of Person (Person::getCity in this example).

Eran
  • 387,369
  • 54
  • 702
  • 768
  • sir, thanks very much. I found I have described it in the second in my question. but mine is too broad. – holi-java May 30 '17 at 11:00