One common requirement in Java application is to iterate through the elements of a collection. Prior to Java 8, the three most common ways to iterate through a collection are by using the while loop, for loop, and enhanced for loop. As the Java Collection interface extends Iterable, you can also use the hasNext() and next() methods of Iterable to iterate through collection elements.
Starting from Java 8, we have a new forEach method in Iterable to loop through elements in a collection – but in a different way.
Below code snippet shows the default implementation of java forEach method in Iterable interface. It makes this method available to all collection classes except Map.
The method performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.
The action represents an operation that accepts a single input argument and returns no result. It is an instance of Consumer interface.
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
A custom action can be created using this simple syntax. Here Object type shall be replaced with the type of elements in the collection or stream.
Consumer<Object> action = new Consumer<Object>()
{
@Override
public void accept(Object t)
{
//perform action
}
};
With both the new forEach method and the Java 8 Stream API, you can create a stream of elements in a collection and then pipeline the stream to a forEach method for iteration.
The code to iterate through a stream of elements in a List is this.
import java.util.List;
import java.util.ArrayList;
public class Example {
public static void main(String[] args) {
List<String> names = new ArrayList<String>();
names.add("Maggie");
names.add("Michonne");
names.add("Rick");
names.add("Merle");
names.add("Governor");
names.stream() //creating stream
.filter(f->f.startsWith("M")) //filtering names that starts with M
.forEach(System.out::println); //displaying the stream using forEach
}
}
For parallel streams, the only difference is that you need to call the parallelStream() method instead of stream() on the List. Then iterate through the stream of elements using forEach, like this.
public static void iterateThroughListParallelStream(List<String> list){
list.parallelStream().forEach(System.out::println);
}
As you can notice, the order in which the list elements are processed is not the order in which the elements are stored in the list. However, when dealing with larger sets of data, parallel streams bring in considerable performance gain to your program.
For sequential streams the order of elements is same as the order in the source, so the output would be same whether you use forEach or forEachOrdered. However when working with parallel streams, you would always want to use the forEachOrdered() method when the order matters to you, as this method guarantees that the order of elements would be same as the source. Lets take an example to understand the difference between forEach() and forEachOrdered().
import java.util.List;
import java.util.ArrayList;
public class Example {
public static void main(String[] args) {
List<String> names = new ArrayList<String>();
names.add("Maggie");
names.add("Michonne");
names.add("Rick");
names.add("Merle");
names.add("Governor");
//forEach - the output would be in any order
System.out.println("Print using forEach");
names.stream()
.filter(f->f.startsWith("M"))
.parallel()
.forEach(n->System.out.println(n));
/* forEachOrdered - the output would always be in this order:
* Maggie, Michonne, Merle
*/
System.out.println("Print using forEachOrdered");
names.stream()
.filter(f->f.startsWith("M"))
.parallel()
.forEachOrdered(n->System.out.println(n));
}
}
In this example, we are iterating an ArrayList using forEach() method. Inside forEach we are using a lambda expression to print each element of the list.
import java.util.List;
import java.util.ArrayList;
public class Example {
public static void main(String[] args) {
List<String> fruits = new ArrayList<String>();
fruits.add("Apple");
fruits.add("Orange");
fruits.add("Banana");
fruits.add("Pear");
fruits.add("Mango");
//lambda expression in forEach Method
fruits.forEach(str->System.out.println(str));
}
}
Map in Java does not extends Iterable and therefore does not inherit Iterable’s forEach. However, Map itself has its own forEach method that you can use to iterate through key-value pairs.
import java.util.Map;
import java.util.HashMap;
public class Example {
public static void main(String[] args) {
Map<Integer, String> hmap = new HashMap<Integer, String>();
hmap.put(1, "Monkey");
hmap.put(2, "Dog");
hmap.put(3, "Cat");
hmap.put(4, "Lion");
hmap.put(5, "Tiger");
hmap.put(6, "Bear");
/* forEach to iterate and display each key and value pair
* of HashMap.
*/
hmap.forEach((key,value)->System.out.println(key+" - "+value));
/* forEach to iterate a Map and display the value of a particular
* key
*/
hmap.forEach((key,value)->{
if(key == 4){
System.out.println("Value associated with key 4 is: "+value);
}
});
/* forEach to iterate a Map and display the key associated with a
* particular value
*/
hmap.forEach((key,value)->{
if("Cat".equals(value)){
System.out.println("Key associated with Value Cat is: "+key);
}
});
}
}
This type of iterator manage the iteration in the background and leaves the programmer to just code what is meant to be done with the elements of the collection.
The iterator instead, manages the iteration and makes sure to process the elements one-by-one.
Let’s see an example of an internal iterator:
names.forEach(name -> System.out.println(name));
In the forEach method above, we can see that the argument provided is a lambda expression. This means that the method only needs to know what is to be done and all the work of iterating will be taken care of internally.
External iterators mix the what and the how the loop is to be done.
Enumerations, Iterators and enhanced for-loop are all external iterators (remember the methods iterator(), next() or hasNext() ? ). In all these iterators it’s our job to specify how to perform iterations.
Consider this familiar loop:
for (String name : names) {
System.out.println(name);
}
Though we are not explicitly invoking hasNext() or next() methods while iterating over the list, the underlying code which makes this iteration work uses these methods. This implies that the complexity of these operations is hidden from the programmer but it still exists.
Contrary to an internal iterator in which the collection does the iteration itself, here we require external code that takes every element out of the collection.
参考:
《Java 8 forEach》
《Java 8 forEach method with example》
《Guide to the Java 8 forEach》
《Java 8 forEach》