The Benefits of the Collection API in Java 8: Part 1

Sep 18, 2019
hackajob Staff

Java 8 has made some significant improvements to the Collection API and in part 1 of this article, we’ve decided to jump right in. We’ll be covering the ‘forEach’ method, the ‘Collection.removeIf’ method, the ‘Iterator.forEachRemaining’ method and the ‘SplitIterator’ interface.

Before reading any further, make sure to check out our previous articles on all-things Java 8. It’ll get you up to speed on what we’ve been working on previously and makes for a great bit of light reading 😀

The forEach Method

Java 8 has added a new method called ‘forEach’ to all of the ‘Collection’ interfaces, via the ‘Iterable’ interface. This method can be used to iterate through a Collection internally without an explicit ‘for’ loop.

Before Java 8

Prior to Java 8, you would have had to have written code similar to the following example in order to iterate through a collection:

List<Integer> list = Arrays.asList(5,10,15,20,25);

for(int num:list)

System.out.print(num+" ");

This example simply prints each number in the list as follows:

5 10 15 20 25

The Java 8 Way

Using Java 8, you can iterate through a Collection by simply using the 'forEach' method. The above code can be written as follows:

List<Integer> list = Arrays.asList(5,10,15,20,25);

list.forEach(num -> System.out.print(num+" "));

When this code is executed, it will print the same output as before:

5 10 15 20 25

This means that you no longer need to write an explicit 'for' loop. Compared to the example created pre-Java 8, this new code is a lot more concise.

How the forEach Method Works

Java 8 has added the ‘forEach’ method on the ‘Iterable’ Interface. The ‘Collection’ interface extends the ‘Iterable’ interface and means that this method is automatically available in all of the ‘Collection’ interfaces, including ‘Set’ and ‘List’.

The ‘forEach’ method accepts a single argument which is also known as a ‘Consumer’ instance. An in-built functional interface, ‘Consumer’ accepts an argument of any data type and operates on it.

In the above example, the ‘Consumer’ interface is implemented via a lambda expression that simply prints the element. The ‘forEach’ method is given a default implementation within the ‘Iterable’ interface, and the default implementation of the ‘forEach’ method iterates through all elements of the ‘Collection’ on which it is invoked; applying the lambda expression to each element.

Collection.removeIf

‘removeIf’ is another new method added to the Collection interface and can be used to remove elements from a Collection that match certain conditions.

Before Java 8

Suppose you want to remove all of the even numbers in a list. Before Java 8, you would’ve had to write code similar to the following:

List<Integer> input = new ArrayList<Integer>();

input.add(7);

input.add(14);

input.add(21);

input.add(28);

List<Integer> output = new ArrayList<Integer>();

for(int num:input){

if(num % 2 != 0){

output.add(num);

}

}

Above, an output list is created with only the odd numbers added to the list.

The Java 8 Way

By choosing to start with Java 8, you can use the ‘Collection.removeIf’ method like so:

List<Integer> input = new ArrayList<Integer>();

input.add(7);

input.add(14);

input.add(21);

input.add(28);

boolean success = input.removeIf(num -> (num%2==0));

if(success){

input.forEach(num -> System.out.print(num+" "));

}

When the above code is executed, it will print the following output:

7 21

Compared to the pre-Java 8 example above, this code is clearer and more concise.

How removeIf Works

The ‘removeIf’ method has been added to Java 8’s Collection interface, with this method accepting a single argument which is a ‘Predicate’ instance. ‘Predicate’ is an in-built functional interface that accepts an argument of any data type and returns a boolean. In the above code, the ‘Predicate’ interface is implemented via a lambda expression that checks whether the number is even.

The ‘removeIf’ method is automatically given a default implementation within the Collection interface. This applies the lambda expression to every element of the Collection and removes those elements that match the condition within the lambda expression.

Remember that the ‘removeIf’ method returns a boolean value which indicates if values are removed from the list. Essentially, even if one element from the input list is removed, this method will return a ‘true’.

The Iterator.forEachRemaining Method

The ‘forEachRemaining’ method has been added to the Iterator interface by Java 8. This method helps in using an ‘Iterator’ over a Collection, without an explicit loop.

Before Java 8

Prior to Java 8, you had to write code similar to the following example in order to use an Iterator:

List<String> input = Arrays.asList("Apple","Mango","Banana");

Iterator<String> itr = input.iterator();

while(itr.hasNext())

System.out.print(itr.next()+" ");

When this code is executed, it will print the following output:

Apple Mango Banana

The Java 8 Way

When using Java 8, you can use the ‘forEachRemaining’ method on the Iterator interface, meaning that the code above can be re-written like so:

List<String> input = Arrays.asList("Apple","Mango","Banana");

Iterator<String> itr = input.iterator();

itr.forEachRemaining(str -> System.out.print(str+" "));

When this code is executed, it will print the same output as before:

Apple Mango Banana

This means that you no longer require a loop in order to iterate through the input list (again via an Iterator).

How the ‘Iterator.forEachRemaining’ Method Works

Java 8 has added a new method called ‘forEachRemaining’ on the Iterator interface. Just like the ‘forEach’ method, the ‘forEachRemaining’ method accepts a Consumer instance as a parameter. In the above example, the Consumer interface is implemented via a lambda expression that simply prints the element.

The ‘forEachRemaining’ method is given a default implementation within the Iterator interface which iterates through all the elements of the Collection on which it is invoked and applies the specified lambda expression. Remember that the ‘forEachRemaining’ method doesn’t provide any other benefit other than eliminating the need to write a ‘while’ loop.

The Collection.SplitIterator Interface

Another new feature that can be found in Java 8, the ‘SplitIterator’ interface can be used for parallel execution and is similar to the ‘Iterator’ interface.

Obtaining a SplitIterator

The ‘SplitIterator’ method has been added to all the Collection interfaces in Java 8 that include ‘Collection’, ‘Set’, ‘List’ etc, and will return a ‘SplitIterator’ instance. The following code shows how this works:

List<String> input = Arrays.asList("Cat","Dog","Mouse");

Spliterator<String> sitr1 = input.spliterator();

The above example obtains a ‘SplitIterator’ over the input list.

Traversing via a SplitIterator

The ‘SplitIterator’ can be used to traverse through the elements of Collection and can do this either individually or in bulk. The ‘SplitIterator’ provides separate methods for doing this, which we’ve outlined in the following sections:

Traversing Individually (via tryAdvance)

Java 8’s ‘tryAdvance’ method can be used to traverse individually through the elements in a collection and must always be used with a loop. The following code demonstrates how this works:

List<String> input = Arrays.asList("Cat","Dog","Mouse”);

Spliterator<String> sitr1 = input.spliterator();

while(sitr1.tryAdvance(str->System.out.print(str+" ")));

As you can see, the ‘tryAdvance’ method accepts a Consumer instance as a parameter. In the above example, it’s implemented via a lambda expression that prints the element.

The ‘tryAdvance’ method then checks if there are any elements left to be processed. If there are, it simply performs the specified action required and returns either ‘true’ or ‘false’. Note that the ‘tryAdvance’ method also advances the iterator. Essentially, it combines the ‘hasNext’ and ‘next’ methods in an ordinary iterator. When there are no more elements left to be processed, the ‘tryAdvance’ method returns a ‘false’ and the loop is exited.

When the example code above is implemented, it will print the following output to the console:

Cat Dog Mouse

Traversing in Bulk (via forEachRemaining)

Just like the iterator interface, the ‘forEachRemaining’ method is also available on the ‘SplitIterator’ interface and can be used to iterate through elements within the input collection, without an explicit loop. The following code shows how this works:

List<String> input = Arrays.asList("Cat","Dog","Mouse");

Spliterator<String> sitr1 = input.spliterator();

sitr1.forEachRemaining(str -> System.out.print(str+" "));

Like the ‘Iterator.forEachRemaining’ method, this particular ‘forEachRemaining’ method also accepts a Consumer instance as a parameter, which can then be implemented via a lambda expression. It’s key to remember that the lambda expression is applied to every element within the input Collection. When you execute the example above, it will print the following output to the console:

Cat Dog Mouse

Note: In addition to the Collection interfaces, the ‘SplitIterator’ method can be used on the Stream interface and can iterate over a Stream also.

Parallel Processing (via a SplitIterator)

The additional benefit that the ‘SplitIterator’ provides over an ‘Iterator’ alone is that it can be used for parallel iteration. In fact, there’s a method called ‘trySplit’ available on the ‘SplitIterator’ interface that can be used to divide the input into separate ‘SplitIterator’ instances. These can then be executed in different threads, as the following code demonstrates:

List<String> input = Arrays.asList("Cat","Dog","Mouse","Lion","Tiger","Elephant");

Spliterator<String> sitr1 = input.spliterator();

Spliterator<String> sitr2 = sitr1.trySplit();

System.out.print("SplitIterator 1: ");

sitr1.forEachRemaining(str -> System.out.print(str+" "));

System.out.println();

System.out.print("SplitIterator 2: ");

sitr2.forEachRemaining(str -> System.out.print(str+" "));

The ‘trySplit’ method splits the iterator into two parts. So when you execute the above code, it will print the following output:

SplitIterator 1: Lion Tiger Elephant

SplitIterator 2: Cat Dog Mouse

In the above code, both the ‘SplitIterators’ are executed in the same thread, but you can execute them in different threads to achieve parallel processing. As well as this, the ‘tryAdvance’ method can also be used for parallel processing by executing each element in a separate thread.

In this article, we covered some of the Collection improvements in Java 8, including ‘forEach’, ‘forEachRemaining’ and ‘Collection.removeIf’, as well as the ‘SplitIterator’ and its features. In part 2 of this article, we’ll be covering further Collection API improvements that have been added to Java 8.

Like what you've read? Sign up to hackajob and discover a full library of coding challenges to complete, all built in our custom IDE.