avatarBen Weidig

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

1858

Abstract

"hljs-punctuation">-></span> l.<span class="hljs-title function_ invoke__">compareTo</span>(r)) <span class="hljs-comment">// 2. Sort</span> .<span class="hljs-title function_ invoke__">filter</span>(s <span class="hljs-punctuation">-></span> s.<span class="hljs-title function_ invoke__">startsWith</span>(<span class="hljs-string">"A"</span>)) <span class="hljs-comment">// 3. Reject</span> .forEach(System.out::println); <span class="hljs-comment">// 4. Do stuff</span> </pre></div><p id="f637">This code will run <code>map</code> 5 times, <code>sorted</code> 8 times, <code>filter</code> 5 times, and <code>forEach</code> 2 times. We got 20 operations to output 2 values.</p><p id="dc45">That’s ridiculous!</p><p id="3286">Well, we can do better than that:</p><div id="dad1"><pre>Stream.<span class="hljs-title function_ invoke__">of</span>(<span class="hljs-string">"ananas"</span>, <span class="hljs-string">"oranges"</span>, <span class="hljs-string">"apple"</span>, <span class="hljs-string">"pear"</span>, <span class="hljs-string">"banana"</span>) .<span class="hljs-title function_ invoke__">filter</span>(s <span class="hljs-punctuation">-></span> s.<span class="hljs-title function_ invoke__">startsWith</span>(<span class="hljs-string">"a"</span>)) <span class="hljs-comment">// 1. Filter first</span> .<span class="hljs-title function_ invoke__">map</span>(<span class="hljs-type">String</span>::toUpperCase) <span class="hljs-comment">// 2. Process</span> .<span class="hljs-title function_ invoke__">sorted</span>((l, r) <span class="hljs-punctuation">-></span> l.<span class="hljs-title function_ invoke__">compareTo</span>(r)) <span class="hljs-comment">// 3. Sort</span> .forEach(System.out::println); <span class="hljs-comment">// 4. Do stuff</span></pre></div><p id="

Options

80bd">By filtering first, we are going to restrict the <code>map/sorted</code> operations to a minimum: <code>filter</code> 5 times, <code>map</code> 2 times, <code>sort</code> 1 time, and <code>forEach</code> 2 times, which saves us 10 operations in total. In this example, it might seem like not a big deal, but usually, we deal with more than just 5 items, and the <code>map</code> operation might be <i>expensive</i> to do, so doing less is always better.</p><h2 id="c78b">Prepare first, then filter, and do stuff later</h2><p id="4d2e">What if you can’t filter first? Well, then it often helps to prepare your data in the first step just a little so you can filter in the next step and only do the <i>heavy</i> processing on the remaining items.</p><div id="9d4f"><pre>Stream.of<span class="hljs-comment">(obj1, obj2, obj3, obj4, obj5)</span> .map<span class="hljs-comment">(this::prepareSoWeCanFilter)</span> <span class="hljs-comment">// 1. Make filterable</span> .filter<span class="hljs-comment">(o -> o.isValid()</span>) <span class="hljs-comment">// 2. Filter</span> .map<span class="hljs-comment">(this::doSomeHeavyProcessing)</span> <span class="hljs-comment">// 3. Heavy processing</span> .forEach<span class="hljs-comment">(System.out::println)</span>; <span class="hljs-comment">// 4. Do stuff</span></pre></div><p id="3ba9">This way you can reduce the heavy operations to be only applied to the objects that actually need it.</p><h2 id="632e">Conclusion</h2><p id="1854">Things to consider when using streams:</p><ul><li>If necessary, prepare data to be more easily filterable.</li><li>Filter first, if possible. Fewer items equals fewer operations along the way.</li><li>If not possible to filter first, try to use cheaper operations first, filter, then more expensive ones.</li></ul></article></body>

Java streams: order matters

Photo by Nathan Dumlao on Unsplash

Streams are one of my most used features of Java 8, especially in combination with lambdas (more about that in another post). They make code more concise, and we all love shorter code which is more readable. But a lot can go wrong along the way…

The beauty of Java streams is the ability to concat multiple operations to a “pipeline”. It can replace most for-loops in your code, especially the ones that are just pushing data from one data structure to another (e.g., from List<YourObject> to Map<String, YourObject>. But you have to remember one thing: every step in the stream will be called until an item is rejected.

Filter first, map later

How many operations will be called in this code?

Stream.of("ananas", "oranges", "apple", "pear", "banana")
      .map(String::toUpperCase)          // 1. Process
      .sorted((l, r) -> l.compareTo(r))  // 2. Sort
      .filter(s -> s.startsWith("A"))    // 3. Reject
      .forEach(System.out::println);     // 4. Do stuff

This code will run map 5 times, sorted 8 times, filter 5 times, and forEach 2 times. We got 20 operations to output 2 values.

That’s ridiculous!

Well, we can do better than that:

Stream.of("ananas", "oranges", "apple", "pear", "banana")
      .filter(s -> s.startsWith("a"))   // 1. Filter first
      .map(String::toUpperCase)         // 2. Process
      .sorted((l, r) -> l.compareTo(r)) // 3. Sort
      .forEach(System.out::println);    // 4. Do stuff

By filtering first, we are going to restrict the map/sorted operations to a minimum: filter 5 times, map 2 times, sort 1 time, and forEach 2 times, which saves us 10 operations in total. In this example, it might seem like not a big deal, but usually, we deal with more than just 5 items, and the map operation might be expensive to do, so doing less is always better.

Prepare first, then filter, and do stuff later

What if you can’t filter first? Well, then it often helps to prepare your data in the first step just a little so you can filter in the next step and only do the heavy processing on the remaining items.

Stream.of(obj1, obj2, obj3, obj4, obj5)
      .map(this::prepareSoWeCanFilter)  // 1. Make filterable
      .filter(o -> o.isValid())         // 2. Filter
      .map(this::doSomeHeavyProcessing) // 3. Heavy processing
      .forEach(System.out::println);    // 4. Do stuff

This way you can reduce the heavy operations to be only applied to the objects that actually need it.

Conclusion

Things to consider when using streams:

  • If necessary, prepare data to be more easily filterable.
  • Filter first, if possible. Fewer items equals fewer operations along the way.
  • If not possible to filter first, try to use cheaper operations first, filter, then more expensive ones.
Functional Programming
Java
Streams
Best Practices
Software Development
Recommended from ReadMedium