Using jOOλ to Combine Several Java 8 Collectors into One

With Java 8 being mainstream now, people start using Streams for everything, even in cases where that’s a bit exaggerated (a.k.a. completely nuts, if you were expecting a hyperbole here). For instance, take mykong’s article here, showing how to collect a Map’s entry set stream into a list of keys and a list of values: http://www.mkyong.com/java8/java-8-convert-map-to-list The code posted on mykong.com does it in two steps:

package com.mkyong.example;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ConvertMapToList {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(10, "apple");
        map.put(20, "orange");
        map.put(30, "banana");
        map.put(40, "watermelon");
        map.put(50, "dragonfruit");

        System.out.println("\n1. Export Map Key to List...");

        List<Integer> result = map.entrySet().stream()
                .map(x -> x.getKey())
                .collect(Collectors.toList());

        result.forEach(System.out::println);

        System.out.println("\n2. Export Map Value to List...");

        List<String> result2 = map.entrySet().stream()
                .map(x -> x.getValue())
                .collect(Collectors.toList());

        result2.forEach(System.out::println);
    }
}

This is probably not what you should do in your own code. First off, if you’re OK with iterating the map twice, the simplest way to collect a map’s keys and values would be this:

List<Integer> result1 = new ArrayList<>(map.keySet());
List<String> result2 = new ArrayList<>(map.values());

There’s absolutely no need to resort to Java 8 streams for this particular example. The above is about as simple and speedy as it gets.
Don’t shoehorn Java 8 Streams into every problem
But if you really want to use streams, then I would personally prefer a solution where you do it in one go. There’s no need to iterate the Map twice in this particular case. For instance, you could do it by using jOOλ’s Tuple.collectors() method, a method that combines two collectors into a new collector that returns a tuple of the individual collections. Code speaks for itself more clearly than the above description. Mykong.com’s code could be replaced by this:

Tuple2<List<Integer>, List<String>> result = 
map.entrySet()
    .stream()
    .collect(Tuple.collectors(
        Collectors.mapping(Entry::getKey, Collectors.toList()),
        Collectors.mapping(Entry::getValue, Collectors.toList())
    ));

The only jOOλ code put in place here is the call to Tuple.collectors(), which combines the standard JDK collectors that apply mapping on the Map entries before collecting keys and values into lists. When printing the above result, you’ll get:
([50, 20, 40, 10, 30], [dragonfruit, orange, watermelon, apple, banana])
i.e. a tuple containing the two resulting lists. Even simpler, don’t use the Java 8 Stream API, use jOOλ’s Seq (for sequential stream) and write this shorty instead:

Tuple2<List<Integer>, List<String>> result = 
Seq.seq(map)
   .collect(
        Collectors.mapping(Tuple2::v1, Collectors.toList()),
        Collectors.mapping(Tuple2::v2, Collectors.toList())
   );

Where Collectable.collect(Collector, Collector) provides awesome syntax sugar over the previous example Convinced? Get jOOλ here: https://github.com/jOOQ/jOOL

16 thoughts on “Using jOOλ to Combine Several Java 8 Collectors into One

  1. nice one! :) we should also underline the fact that every time we create a Stream from a List, we actually create two objects: Spliterator first and Stream then (and other bunch of stuff when we do some stream operations).

    PS: about your lib, I would love if it had something similar:

    Tuple.tuple(... N objects ...)
         .filter(... N objects -> condition ...)
         .transform/map(... N objects again -> to something else)
         .orElse/otherwise(... else-worlds ...)
    

    what do you think? or there’s something similar?

    1. I’m sure there are even more objects being created under the hood as you said. Here’s some nice insight:
      http://blog.codefx.org/java/stream-performance

      Hmm, do you mind elaborating a bit on your tuple / filter / map idea? Do you mean creating stuff like

      Tuple3<Seq, Seq, Seq> streams = ...
      streams.filter(i -> ..., s -> ..., l -> ...)
      

      What would be the use-case?

      1. definitely I just recall the most quoted ones :)

        well I dunno if it can be a useful use-case, sometimes I have to «validate» a tuple of objects against precise conditions and if all them are ok, then I can go on (i.e. I can transform a tuple to an object). For instance just a trivial example:

        static Person create(BirthName birthName, FamilyName familyName) {
          if (birthName.getValue().length() < 20 && familyName.getValue().length()  b.getValue().length() < 20 && f.getValue().length()  ...);
        }
        

        do you think it’s feasible and acceptable? or I’m drunk?

        1. Thanks for the explanations. Hmm, and why would you want to “destructure” that tuple and run filters on each individual attribute? Would that somehow make your code more readable? But what if a filter predicate needs access to several attributes?

          I’m sorry, I’m not quite sure if I understand the pain point here (yet)…

          1. Well the idea was to transform (or other op.s) a tuple to another object by using its objects set. A monad tuple maybe? Yeah perhaps it’s just a more comfortable way to threat with a objects set (i.e. method parameters) and not a very wide use case.

  2. Hi Lukas,

    nice writeup and thanks for the example ;) With Seq you created a great API that integrates with Java’s new Stream API on the one hand and which extends it by adding missing functionality on the other hand. It is the best API on top of Stream I can imagine.

    The Stream API is great when elements of a Collection are manipulated in a linear way. Stream is a funky form of an Iterator, it is supposed to process a _stream_ of elements by intermediate operations and finally returns an accumulated or collected result.

    What I really miss in Java is a modern collection library. Stream is just a processor on top of the old-fashioned collections.

    With Javaslang’s collections, which are very similar to Scala’s, I just do a

    map.unzip(t -> t);

    Thats all. Extracted two sequences (keys, values) from a map. That’s the way I expect a modern collection library to work.

    Greets

    Daniel

    1. Stream is just a processor on top of the old-fashioned collections.

      That’s not true.

      Stream is a funky form of an Iterator

      That’s more like it. A Stream can also operate on Javaslang collections. Of course, you don’t need that as you have your own Stream. But from an interop perspective, it would be perfectly possible and not unreasonable to do so.

      Anyway, I agree there could be more goodies in the JDK collections. In fact, I’m the last one to disagree with you on this one. :)

  3. A bit off topic, but I was not able to find a relevant blog post for it: In a Quora answer, you mentioned: “Unfortunately (in my opinion), Java’s Streams are also heavily biased towards a feature that shouldn’t be as central to streams as it is: Parallelism.” I personally have only used Java 8 Streams for trivia school assignments, as I’ve been using JS for most of projects for the past year, so it’s possible I haven’t used it enough to make the same observation. But I’m failing to understand how the Streams API is biased towards parallelism. Googling it, I was able to find a DZone article: “If you listen to people from Oracle talking about design choices behind Java 8, you will often hear that parallelism was the main motivation. Parallelization was the main driving force behind lambdas, stream API and others.” but it doesn’t explain how the API is biased towards parallelism, in terms of usage. How does this biasness affect a developer using the Streams API?

    1. Hi Abdul,

      Thanks for your message. That DZone article you’ve found has also been written by me. So, you’re now suffering from confirmation bias in addition to parallelisation bias :).

      The explanation is simple. The Java 8 EG wanted to implement an API that makes using the Java 7 ForkJoinPool extremely simple through Streams. Which makes total sense, other languages / libraries have parallel streams or parallel collections as well. But getting parallelism right is extremely hard, especially when you design an API. Conversely, there would be a lot of streams API functionality that would be extremely useful on sequential only streams, which cannot be implemented (easily) on a potentially parallel stream. One example is “zipWithIndex()” or “zip” in general.

      My claim is simply that consumers of Java 8 Streams would have probably preferred more sophisticated sequential streams over rather simple parallel streams. I’ve recently created a poll on Twitter to confirm this – again biased because I asked my followers who tend to agree with me more often than a more random sample of developers:
      https://twitter.com/lukaseder/status/778153514300272640

      I hope this helps,
      Lukas

      1. “But getting parallelism right is extremely hard” _is_ a recurring notion that I had read regarding the Stream API’s parallel stream functions. I think more reading will help me appreciate and understand your response more effectively

  4. Also, let’s hypothetically say that HashMap didn’t have the methods values() and keyset(), which only returns a field variable, making it obviously faster than the Stream approach: To get a List of Keys and Values, you’d have to iterate through the Map, and collect into a List. In that case, would the use of Streams demonstrated by mykong be nonsensical?

    1. mykong’s demonstration is not “nonsensical”. His approach may be valid in some cases. The criticism here is that there are many blogs out there that optimise for SEO to get traffic. Thus, they contain content that is often not the best choice. There would have been many more interesting use-cases for streams than the one he displayed, which in this particular case is shoehorning streams into a random example use-case where streams don’t fit too well.

      This article here merely criticises the fact that people often use “new technology” or “new functionality” without reflecting on existing functionality, just because using the new stuff makes them feel better.

Leave a Reply to AbdulCancel reply