Friday, 13 October 2017

Java 8 | forEach() method in Iterable and Stream interface

Java 8 has introduced forEach(), a new way to loop over a List or Collection, by using the forEach() method of the new Stream class. It is defined in Iterable and Stream interface. It is a default method defined in the Iterable interface.

default void forEach(Consumer<? super T> action) {

    Objects.requireNonNull(action);

    for (T t : this) {

        action.accept(t);

    }

}

Collection classes which extend Iterable interface can use a forEach loop to iterate elements. This method takes a single parameter which is a functional interface so it can be passed t lambda expression as an argument.

Before Java8, we need to create an Iterator to traverse through a Collection. We need to create an Iterator whose whole purpose is to iterate over and then we have business logic in a loop for each of the elements in the Collection. We might get ConcurrentModificationException if the iterator is not used properly.

Java 8 has introduced the forEach method which helps us to focus on business logic only. forEach method takes java.util.function.Consumer object as an argument, so it helps in having our business logic at a separate location that we can reuse.
Example:
package com.java8;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.lang.Integer;

/**
 * This class contains the Java8 forEach loop.
 *
 * @author algos.debug
 */
public class Java8ForEach {

     public static void main(String[] args) {

           // creating sample Collection
           List<Integer> myList = new ArrayList<Integer>();
           for (int i = 0; i < 10; i++) {
                myList.add(i);
           }

           /* traversing using Iterator */
           System.out.println("\nIterator Value ");
           Iterator<Integer> it = myList.iterator();
           while (it.hasNext()) {
                Integer i = it.next();
                System.out.print(" " + i);
           }

           System.out.println("\nforEach anonymous class Value ");
           /* traversing through forEach method of Iterable with anonymous class. */
           myList.forEach(new Consumer<Integer>() {
                public void accept(Integer t) {
                     System.out.print(" " + t);
                }
           });

           System.out.println("\nforEach with Consumer interface implementation ");
           /* traversing with Consumer interface implementation. */
           MyConsumer action = new MyConsumer();
           myList.forEach(action);

           System.out.println("\nforEach with Lambda ");
           /* Traversing forEach with Lambda. */
           myList.forEach((a) -> {
                System.out.print(" " + (a));
           });

     }

}

/**
 * Consumer implementation that can be reused.
 *
 * @author algos.debug
 */
class MyConsumer implements Consumer<Integer> {
     public void accept(Integer t) {
           System.out.print(" " + t);
     }

}

One more thing to remember about the forEach() method is that it's a terminal operation, which means you cannot reuse the Stream after calling this method. It will throw IllegalStateException if you try to call another method on this Stream.
package com.java8;

import java.util.Arrays;
import java.util.stream.Stream;

/**
 * @author algos.debug
 *
 */
public class ForeachStreamException {

     public static void main(String[] args) {

           String[] array = { "a", "b", "c", "d", "e" };
           Stream<String> stream = Arrays.stream(array);

           /** loop a stream .*/
           stream.forEach(x -> System.out.print(" "+x));

           System.out.println();
          
           /** Reuse it to filter again, throws IllegalStateException .*/
           long count = stream.filter(x -> "b".equals(x)).count();
           System.out.println(count);
     }
}

Output:
      a b c d e
      Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
          at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
          at java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94)

Advantages of forEach() over for traditional  loop in Java 8 
1.     More succinct/crisp code, sometimes just one-liner. Sometimes the number of lines might increase but the forEach method helps in having the logic for iteration and business logic at separate place resulting in higher separation of concern and cleaner code.
2.      It can be passed lambda expression, which gives us the immense flexibility to change in the loop.
3.      forEach looping can be made parallel with minimal effort e.g. without writing a single line of concurrent code, all we need to do is call parallelStream() method. 

Important points about forEach() method
1.      forEach() method is defined at two places, on Iterable interface as well as on Stream class. So we can use with Collection and streams.
Example: list.forEach() and list.stream.forEach
2.      forEach() should be preferred with streams because streams are lazy and not evaluated until a terminal operation is called. 
3.      forEach() is a terminal operation, Calling any method on stream after this will lead to IllegalStateException.
4.      When you run forEach() method on parallel stream the order on which elements are processed is not guaranteed, though you can use forEachOrdered() to impose ordering.
5.      forEach() method accepts a Consumer instance, which is a functional interface so It can be passed to lambda expression. 

1 comment:

  1. Awesome blog. I enjoyed reading your articles. This is truly a great read for me. I have bookmarked it and I am looking forward to reading new articles. Keep up the good work! gostream

    ReplyDelete

Related Posts Plugin for WordPress, Blogger...