2

I stumbled upon a task with a stream that I couldn't solve. I had to modify the code to print abcABC instead of aAbBcC.

I understand why it's printed this way.

// prints 'a b c' then prints 'A B C'
List<String> strings = List.of("a", "b", "c");
    strings.stream()
           .peek(str -> System.out.println(str))
           .map(str -> str.toUpperCase())
           .forEach(str -> System.out.println(str));

I appreciate any help.

Eran
  • 387,369
  • 54
  • 702
  • 768
mihu
  • 37
  • 5

1 Answers1

3

Here's one way to do it:

List<String> strings = List.of("a", "b", "c");
    strings.stream()
           .peek(str -> System.out.println(str))
           .sorted()
           .map(str -> str.toUpperCase())
           .forEach(str -> System.out.println(str));

The sorted() operation must process all the elements of the Stream before the terminal operation can start outputting the elements (since you can't sort a Stream without going over all its elements).

Therefore, this causes peek() on all the elements to be executed before the first upper case element is outputted by forEach().

Eran
  • 387,369
  • 54
  • 702
  • 768
  • Is this actually guaranteed, or could the sorting be theoretically omitted if the implementation can determine that the input is already sorted? – Hulk Nov 18 '21 at 10:24
  • I guess such an optimization is not possible here, as [java.util.stream](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/package-summary.html#StreamOps) explicitly uses it as an example for an intermediate operation that needs to process the entire input, but I was thinking along the lines of how `.count()` can be optimized if the size is known. – Hulk Nov 18 '21 at 10:31
  • 3
    @Hulk this optimization does already exist, try `Set strings = new TreeSet<>( List.of("a", "b", "c")); strings .stream() .peek(System.out::println) .sorted() .map(String::toUpperCase) .forEach(System.out::println);` – Holger Nov 18 '21 at 12:18
  • @Holger thanks, interesting - that is exactly what I had in mind. So the trick/hack used in this answer only works for streams based on Spliterators that do not report [Spliterator.SORTED](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Spliterator.html#SORTED). – Hulk Nov 18 '21 at 12:26
  • 1
    @Holger that;s good to know – Eran Nov 18 '21 at 12:51
  • 2
    @Hulk in fact, even the answer’s example is not guaranteed to work, formally. `forEach` is an *unordered* operation, so it’s not required to do the preceding `sorted()` operation. Or, well, whether the programmer should be burdened with such a surprising behavior, is debatable. As Brian Goetz says in [this comment](https://stackoverflow.com/questions/30843279/30916033#comment49971789_30916033), the case of chaining an unordered operation after `sorted` is special and has been exempted from such optimization deliberately, to simplify the implementation (and solve related bugs). – Holger Nov 18 '21 at 13:20
  • Oh yes, I missed that - strictly speaking, [`forEachOrdered()`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Stream.html#forEachOrdered(java.util.function.Consumer)) would be needed, also discussed here: https://stackoverflow.com/a/34253279/2513200 – Hulk Nov 18 '21 at 13:28