Item 45: Use streams judiciously

Introduction

Streams in Java 8 support functional-style operations on streams of elements. This API makes it easier to perform bulk operations, sequentially or in parallel. When used appropriately, streams can make programs shorter and clearer. When used inappropriately, they can make programs difficult to read and maintain.

When to use streams?

Streams are a good candidate to achieve the following things:

  • Uniformly transform sequences of elements
  • Filter sequences of elements
  • Combine sequences of elements using a single operation (for example to add them, concatenate them, or compute their minimum)
  • Accumulate sequences of elements into a collection, perhaps grouping them by some common attribute (e.g.: java.util.stream.Collectors)
  • Search a sequence of elements for an element satisfying some criterion
Overuse of streams

In this section, we will discover how overusing streams make programs hard to read and maintain. Let’s look at the following example from the book:

// Overuse of streams - don't do this!
public class StreamAnagrams {
    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(
                    groupingBy(word -> word.chars().sorted()
                            .collect(StringBuilder::new,
                                    (sb, c) -> sb.append((char) c),
                                    StringBuilder::append).toString()))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupSize)
                    .map(group -> group.size() + ": " + group)
                    .forEach(System.out::println);
        }
    }
}

The code above is verbose, not readable, and difficult to understand for programmers who don’t have a background in streams. First, grouping the words by their alphabetized form is too long and verbose. We can achieve the same task using a helper function. Second, the map operator is redundant.

Precise use of streams

In this section, we are going to learn how to use streams in a clear and concise way. Lets’s look at the new version of the previous example:

public class HybridAnagrams {
    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(Collectors.groupingBy(word -> alphabetize(word)))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupSize)
                    .forEach(g -> System.out.println(g.size() + ": " + g));
        }
    }

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}

You can use these recommendations to write precise streams:

Using helper methods improves the readability in stream pipelines because pipelines lack explicit type information and named temporary variables. For instance, alphabetize method is used for grouping words by their alphabetized form.

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }

In the absence of explicit types, careful naming of lambda parameters is essential to the readability of stream pipelines. For example, parameter g would be named as a group but in forEach() function.

forEach(group -> System.out.println(group .size() + ": " + group));

Ideally, you should avoid using streams to process char values. We could implement the alphabetize method with streams yet it would be more difficult and slower. The foremost reason is that Java doesn’t support for primitive char streams. To give an example, following code prints 721011081081113211911111410810033.

"Hello world!".chars().forEach(System.out:: print);
Conclusion

In this item, we compared the precise and overuse of streams in Java 8. We discovered that when used properly, streams can make programs shorter and clearer. Otherwise, they can make programs difficult to read and maintain.

Leave a Reply

Your email address will not be published. Required fields are marked *