"After all, the engineers only needed to refuse to fix anything, and modern industry would grind to a halt." -Michael Lewis

Enable Massive Growth

The Java Stream API: An Introduction to Collecting Results

Oct 2018

You can view the sample code associated with this post on GitHub.

Calling collect(..) on a stream terminates a stream into a collection. We've already seen that calling collect(Collectors.toList()) moves your stream into a List<T>, but you can also collect into a set. If we take our familiar collection of names in a String collection:

public static List<String> getListOfNames() {
    List<String> names = new ArrayList<>();

    names.add("John");
    names.add("Jacob");
    names.add("Jerry");
    names.add("Josephine");
    names.add("Janine");
    names.add("Alan");
    names.add("Beverly");

    return names;
}

We can make a set out of them like so:

@Test
public void collect_toSet() {
    Set<String> allJNames = names.stream().filter(name -> name.startsWith("J")).collect(Collectors.toSet());

    assertTrue(allJNames.contains("John"));
    assertTrue(allJNames.contains("Jacob"));
}

You can join a Stream without using a delimiter:

@Test
public void collect_joining() {
    String allNamesJoined = names.stream().collect(Collectors.joining());

    assertTrue(allNamesJoined.startsWith("JohnJacobJerry"));
}

Or you can include a delimiter:

@Test
public void collect_joinWithDelimiter() {
    String commaDelimitedNames = names.stream().collect(Collectors.joining(","));

    assertTrue(commaDelimitedNames.startsWith("John,Jacob,Jerry"));
}

Let's move into a slightly more involved example. In a POJO class like so:

public class SimplePair {

    private String name;
    private int id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "name-" + name + ",id-" + id;
    }
}

And where we create pairs that have (id: 1, name: pair1), (id: 2, name: pair2)....

public static List<SimplePair> generateSimplePairs(int numToGenerate) {
    List<SimplePair> pairs = new ArrayList<>();
    for (int i = 1; i <= numToGenerate; i++) {
        SimplePair pair = new SimplePair();

        pair.setId(i);
        pair.setName("pair" + i);

        pairs.add(pair);
    }
    return pairs;
}

We can then collect after a call to map(..), as map continues along the stream:

@Test
public void collect_mapToString() {
    List<SimplePair> twoPairs = TestUtils.generateSimplePairs(2);

    String semiColonDelimited = twoPairs.stream().map(Objects::toString).collect(Collectors.joining(";"));

    assertEquals("name-pair1,id-1;name-pair2,id-2", semiColonDelimited);
}

Finally, we can collect a summary of statistics about certain primitive types. If we wanted statistics about the ids of the collection, we could get them with a call to collect(Collectors.summarizingInt(SimplePair::getId)):

@Test
public void collectStatistics() {
    IntSummaryStatistics statistics = simplePairs.stream().collect(Collectors.summarizingInt(SimplePair::getId));

    assertEquals(5, statistics.getCount());
    assertEquals(5, statistics.getMax());
    assertEquals(1, statistics.getMin());
    assertEquals(15, statistics.getSum());
}

Nick Fisher is a software engineer in the Pacific Northwest. He focuses on building highly scalable and maintainable backend systems.