Pages

Saturday, August 1, 2020

Java 8 Iterable.forEach() vs foreach loop with examples

1. Overview


In this article, you'll learn what are the differences between the Iterator.forEach() and the normal foreach loop before java 8.

First, let us write a simple program using both approaches then you will understand what you can not achieve with the java 8 forEach.

Iterable is a collection api root interface that is added with the forEach() method in java 8. The following code is the internal implementation. Whatever the logic is passed as lambda to this method is placed inside Consumer accept() method. Just remember this for now. When you see the examples you will understand the problem with this code.
default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
Java 8 Iterable.forEach() vs foreach loop with examples


forEach() can be implemented to be faster than the for-each loop, because the iterable knows the best way to iterate its elements, as opposed to the standard iterator way. So the difference is loop internally or loops externally. But there are many drawbacks of using loop internally.

2. Normal forEach() Example


In the below program, We are using foreach to add a List of objects to a collection.

import java.util.ArrayList;
import java.util.List;

public class NormalForEachExample {

	public static void main(String[] args) {

		List<String> list = new ArrayList<>();

		for (int i = 0; i < 10; i++) {

			list.add("Item " + i + 1);
		}

		System.out.println("Normal for loop program : " + list);

	}

}
Output:

Normal for loop program : [Item 01, Item 11, Item 21, Item 31, Item 41, Item 51, Item 61, Item 71, Item 81, Item 91]

3. Another Example to iterate the List using for loop


import java.util.ArrayList;
import java.util.List;

public class NormalForEachListExample {

	public static void main(String[] args) {

		List<String> list = new ArrayList<>();

		list.add("one");
		list.add("two");
		list.add("three");
		list.add("four");
		list.add("five");
		
		for(String s : list) {
			System.out.println(s);
		}

	}

}
Output:

one
two
three
four
five

4. Java 8 Iterate or Stream forEach Example


Below an example on java 8 forEach() to add values to List. This approach really makes the code simple and easy to write to the developers.
import java.util.ArrayList;
import java.util.List;

public class Java8ForEachListAddExample {

	public static void main(String[] args) {

		List<String> list = new ArrayList<>();
		list.add("One");

		list.forEach((s) -> list.add(s + 1));

		System.out.println("Final list : " + list);

	}

}
Output:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList.forEach(ArrayList.java:1260)
	at NormalForEachExample.main(NormalForEachExample.java:11)

This code does not work as we are trying to add the values inside the forEach() method and it internally checking the mod count validation and it is failed. Hence, Thrown the ConcurrentModificationException.


So, Here java 8 forEach() does not work to add the values to list. But the normal loops works without any issues.

5. Java 8 Stream or Iterable forEach() Example to Print the values


import java.util.ArrayList;
import java.util.List;

public class Java8ForEachPrint {

	public static void main(String[] args) {

		List<String> list = new ArrayList<>();
		list.add("you");
		list.add("me");
		list.add("learn");
		list.add("java");

		list.forEach(value -> System.out.println(value));

	}

}
Output:

you
me
learn
java

6. Iterable.forEach() vs foreach - Drawback 1: Accessing Values inside forEach declared outside


Creating a simple program to access the String inside the forEach() method where the string is created outside forEach().


import java.util.ArrayList;
import java.util.List;

public class NormalForEachExample {

	public static void main(String[] args) {

		List<String> list = new ArrayList<>();
		list.add("you");
		list.add("me");
		list.add("learn");
		list.add("java");
		
		String newString = "I am newly created";

		list.forEach(value -> {
			System.out.println(value);
			newString = "I am modified";
		});

	}

}
Compile and see what is the problem here is.
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	Local variable newString defined in an enclosing scope must be final or effectively final

The compilation is failed because of value modification to newString variable. That means once any variable is created outside the forEach() method can not be modified.

So, this is the drawback and can not use java 8 forEach().

7. Drawback 2: Java 8 foreach return value


Let us create a simple program that needs to return a value from the forEach() loop.


First, see with the normal for loop and which works perfectly fine without any errors.

	private static boolean hasListHasValueLengh5(List<String> list) {

		for (int i = 0; i < list.size(); i++) {

			if (list.get(i).length() >= 5) {
				return true;
			}

		}
		return false;
	}
Next is to try with the forEach() method. Internally, the forEach() method takes Consumer as an argument and it just takes the parameter but does not return any value. So, technically is impossible to return a value from the forEach() method.

But, you can do with filters and other methods but you need to use the many functions for a small one.
boolean hasLength5 =list.stream().filter( value -> value.length() >=5).findFirst().orElse(null).length() > 0;
If you are new to java 8 then it is a bit difficult to understand so better not to use for simple cases.

8. Drawback 3: Can't handle checked exceptions


forEach() method can not handle the checked exceptions.

Lambdas aren't actually forbidden from throwing checked exceptions, but common functional interfaces like Consumer don't declare any. Therefore, any code that throws checked exceptions must wrap them in try-catch or Throwables.propagate(). But even if you do that, it's not always clear what happens to the thrown exception. It could get swallowed somewhere in the guts of forEach()


	list.forEach(value -> {

			if (value.length() == 2) {
				Class.forName("com.NoClass");
				throws new RuntimeException("Length should be min 3 characters");
			}
		});

This will show the compile-time error and saying can not use throws keyword.

Class.forName("com.NoClass") throws checked exception and wants to throw to the caller but lambda enforces to wrap inside try/catch block. This is also a drawback.

9. Reaming Drawbacks with Collection forEach() method


There are many other areas to consider before using the Lambdas or forEach() method

Can be executed in parallel, so must be careful before enabling the concurrent calls. So, Any parallel code has to be thought through (even if it doesn't use locks, volatiles, and other particularly nasty aspects of traditional multi-threaded execution). Any bug will be tough to find.

Might hurt performance, because the JIT can't optimize forEach()+lambda to the same extent as plain loops, especially now that lambdas are new.

Makes debugging more confusing, because of the nested call hierarchy and, god forbid, parallel execution. The debugger may have issues displaying variables from the surrounding code, and things like step-through may not work as expected.

Streams in general are more difficult to code, read, and debug. Actually, this is true of complex "fluent" APIs in general.

10. Conclusion


In this article, We've seen the main differences between the Iterable.forEach() vs foreach methods. It is better to avoid the stream forEach() incase if you want to do validation or return some values.

It is good to go with the tradition forEach loop over the Java 8 forEeach() loop.


No comments:

Post a Comment

Please do not add any spam links in the comments section.