Rewriting Nested Ifs With Java 8 Stream

Say I have these numbers in an array, for example:

private final List<Integer> numberList = Arrays.asList(0, 1, 2, 3, null, 4, null, 5, 6, 7, 8, 9, 10);

I want to do something with that set of numbers, such as taking out null values, and extracting only the odd ones. Too take it further, maybe I want an even number or two mixed in the processed list as well.

So I have this block of code with a loop and some nested if-else statements in it that will do just that.

Method 1

1 public void forEachWithIfs() {
2        List<Integer> mixedList = new ArrayList<>();
3
4        numberList.forEach(number -> {
5
6           if (number != null ) {
7                if (number %2 == 0) {
8                    if (number == 2 || number == 8) {
9                        mixedList.add(number);
10                   }
11               } else {
12                   mixedList.add(number);
13               }
14
15          } else {
16               log.info("error: found null");
17          }
18      });
19
20      log.info("MIXED: {}", mixedList);
21
22 }

First I check for null values on line #6. The list is using Integer, not int (primitive). Nulls are possible. Then next if is to check that the number is an even or an odd. When it’s the former then the 3rd if determines whether that number has a value of 2 or 8, before adding them to the processed list (mixedList). This is sort of the traditional way of writing conditions within a loop.

I’d like to say that the innermost if at line #8 can be joined with the one before it at line #7. For readability (Or to add another layer of if on purpose, and for this example’s sake because I couldn’t think of any at the time of this writing 😅) I didn’t. Line 7 can be skipped too, and instead check directly for numbers 2 and 8.

Now, I want to use Java 8’s Stream API to get rid of those nested if statements as much as possible. Well not so much as get rid all of it but more like organize those ifs, avoid the nesting where possible. This can be done by using the Java Stream filter() method.

A filter works as its name says, it weeds out anything from that list that matches what you tell it to do.

This is what I came up with using filters and finally a collector, with the use of the Java Stream collect() method,

Method 2

1 public void streamAndCollect() {
2        List<Integer> mixedList = numberList.stream()
3                .filter(this::isNotNull)
4                .filter(this::myOtherFilter)
5                .collect(Collectors.toList());
6
7        log.info("MIXED: {}", mixedList);
8
9 }

To break it down…

1st if – Take out null values. That is at line #3. I’m using my own method to check for null objects. Because I’m matching what the first method does, and that is log something when a null is encountered.

     private boolean isNotNull(Integer i) {
        if (i != null) {
            return true;
        }
        log.info("error: found null");
        return false;
    }

It can be simplified to the filter below, without having to log anything. That’s a built-in null check method in Java.

filter(Objects::nonNull)

If I really wanted to log those nulls but avoid that extra method, I can use peek() instead. With a slight alteration it will look like so,

1 public void streamAndCollect() {
2        List<Integer> mixedList = numberList.stream()
3                .peek(i -> {
4                    if (i == null) log.info("error: found null by filter");
5                })
6                .filter(Objects::nonNull)
7                .filter(this::myOtherFilter)
8                .collect(Collectors.toList());
9
10        log.info("MIXED: {}", mixedList);
11
12 }

The peek() added at lines #3 to #5 just peeks, for the lack of a better term. Java Stream’s Peek method supposedly is there for debugging purposes, or to visually aid in seeing the flow of the pipeline as it goes past certain points with the given logic put into it in this case.

The 2nd and 3rd – Check if even, only allow 2 and 8. Those ifs have been combined and written in a more simplified way. Although I would like to mention the readability aspect suffers a bit. However, I do think it’s just a matter of familiarity when writing it in this form.

    private boolean myOtherFilter(Integer i) {
        return i % 2 == 1 || ((i % 2 == 0) && (i == 2 || i == 8));
    }

The above may also be written as:

return number % 2 == 1 || (number == 2 || number == 8);

The resulting set of numbers for either methods (1 and 2) should be the same:

MIXED: [1, 2, 3, 5, 7, 8, 9]

Also, the code below is valid. Notice the stream() and forEach(). It’s redundant. I am guilty of doing that every now and then.

// Other code omitted for brevity
    public void streamThenForEach() {
        list.stream()
                .forEach(x -> {
                    // Do something
                });
    }

This is better,

// Other code omitted for brevity
    public void justForEach() {
        list.forEach(x -> {
                    // Do something
                });
    }

UNLESS, filter something, THEN, forEach() when the situation really calls for that.

// Other code omitted for brevity
    public void streamFilterThenForEach() {
        list.stream()
                .filter( // Do something )
                .forEach(x -> {
                    // Do main processing
                });
    }

Similar Posts: