In Java 8, we can use the new forEach to loop or iterate a Map, List, Set, or Stream.
1. Loop a Map
1.1 Below is a normal way to loop a Map.
public static void loopMapClassic() {
Map<String, Integer> map = new HashMap<>();
map.put("A", 10);
map.put("B", 20);
map.put("C", 30);
map.put("D", 40);
map.put("E", 50);
map.put("F", 60);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key : " + entry.getKey() + ", Value : " + entry.getValue());
}
}
1.2 In Java 8, we can use forEach to loop a Map and print out its entries.
public static void loopMapJava8() {
Map<String, Integer> map = new HashMap<>();
map.put("A", 10);
map.put("B", 20);
map.put("C", 30);
map.put("D", 40);
map.put("E", 50);
map.put("F", 60);
// lambda
map.forEach((k, v) -> System.out.println("Key : " + k + ", Value : " + v));
}
Output
Key : A, Value : 10
Key : B, Value : 20
Key : C, Value : 30
Key : D, Value : 40
Key : E, Value : 50
Key : F, Value : 60
1.3 For the Map‘s key or value containing null, the forEach will print null.
public static void loopMapJava8() {
Map<String, Integer> map = new HashMap<>();
map.put("A", 10);
map.put("B", 20);
map.put("C", 30);
map.put(null, 40);
map.put("E", null);
map.put("F", 60);
// ensure map is not null
if (map != null) {
map.forEach((k, v) -> System.out.println("Key : " + k + ", Value : " + v));
}
}
Output
Key : null, Value : 40
Key : A, Value : 10
Key : B, Value : 20
Key : C, Value : 30
Key : E, Value : null
Key : F, Value : 60
P.S The normal way to loop a Map will print the same above output.
1.4 If we do not want to print the null key, add a simple null checking inside the forEach.
public static void loopMapJava8() {
Map<String, Integer> map = new HashMap<>();
map.put("A", 10);
map.put("B", 20);
map.put("C", 30);
map.put(null, 40);
map.put("E", null);
map.put("F", 60);
map.forEach(
(k, v) -> {
// yes, we can put logic here
if (k != null){
System.out.println("Key : " + k + ", Value : " + v);
}
}
);
}
Output
Key : A, Value : 10
Key : B, Value : 20
Key : C, Value : 30
Key : E, Value : null
Key : F, Value : 60
2. Loop a List
2.1 Below is a normal way to loop a List.
public static void loopListClassic() {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
// normal loop
for (String l : list) {
System.out.println(l);
}
}
2.2 Java 8 forEach to loop a List.
public static void loopListJava8() {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
// lambda
// list.forEach(x -> System.out.println(x));
// method reference
list.forEach(System.out::println);
}
Output
A
B
C
D
E
2.3 This example filters the null value of a List.
public static void loopListJava8() {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add(null);
list.add("D");
list.add("E");
// filter null value
list.stream()
.filter(Objects::nonNull)
.forEach(System.out::println);
}
Output
A
B
D
E
P.S The forEach for Set and Stream works the same way.
3. forEach and Consumer
3.1 Review the forEach method signature, it accepts a functional interface Consumer.
Iterable.java
public interface Iterable<T> {
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
//..
}
Stream.java
public interface Stream<T> extends BaseStream<T, Stream<T>> {
void forEach(Consumer<? super T> action);
//...
}
3.2 This example creates a Consumer method to print String to its Hex format. We can now reuse the same Consumer method and pass it to the forEach method of List and Stream.
ForEachConsumer.java
package com.favtuts.java8.misc;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class ForEachConsumer {
public static void main(String[] args) {
List<String> list = Arrays.asList("abc", "java", "python");
Stream<String> stream = Stream.of("abc", "java", "python");
// convert a String to a Hex
Consumer<String> printTextInHexConsumer = (String x) -> {
StringBuilder sb = new StringBuilder();
for (char c : x.toCharArray()) {
String hex = Integer.toHexString(c);
sb.append(hex);
}
System.out.print(String.format("%n%-10s:%s", x, sb.toString()));
};
// pass a Consumer
list.forEach(printTextInHexConsumer);
System.out.println();
stream.forEach(printTextInHexConsumer);
System.out.println();
}
}
Output
abc :616263
java :6a617661
python :707974686f6e
abc :616263
java :6a617661
python :707974686f6e
4. forEach and Exception handling.
4.1 The forEach is not just for printing, and this example shows how to use forEach method to loop a list of objects and write it to files.
ForEachWriteFile.java
package com.favtuts.java8.misc;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
public class ForEachWriteFile {
public static void main(String[] args) {
ForEachWriteFile obj = new ForEachWriteFile();
obj.save(Paths.get("/home/tvt/workspace/favtuts/dummies"), obj.createDummyFiles());
}
public void save(Path path, List<DummyFile> files) {
if (!Files.isDirectory(path)) {
throw new IllegalArgumentException("Path must be a directory");
}
files.forEach(f -> {
try {
int id = f.getId();
// create a filename
String fileName = id + ".txt";
Files.write(path.resolve(fileName),
f.getContent().getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
});
}
public List<DummyFile> createDummyFiles() {
return Arrays.asList(
new DummyFile(1, "hello"),
new DummyFile(2, "world"),
new DummyFile(3, "java"));
}
class DummyFile {
int id;
String content;
public DummyFile(int id, String content) {
this.id = id;
this.content = content;
}
public int getId() {
return id;
}
public String getContent() {
return content;
}
}
}
The above program will create three text files under the directory: /home/tvt/workspace/favtuts/dummies.
1.txt
hello
2.txt
world
3.txt
java
4.2 The Files.write may throws IOException, and we must catch the exception inside the forEach; thus, the code looks ugly. The common practice is to extract the code to a new method.
public void save(Path path, List<DummyFile> files) {
if (!Files.isDirectory(path)) {
throw new IllegalArgumentException("Path must be a directory");
}
// extract it to a new method
/*files.forEach(f -> {
try {
int id = f.getId();
// create a filename
String fileName = id + ".txt";
Files.write(path.resolve(fileName),
f.getContent().getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
});*/
// nice!
files.forEach(f -> saveFile(path, f));
}
public void saveFile(Path path, DummyFile f) {
try {
int id = f.getId();
// create a filename
String fileName = id + ".txt";
Files.write(path.resolve(fileName),
f.getContent().getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
Now, we also can write code like this:
ForEachWriteFile obj = new ForEachWriteFile();
Path path = Paths.get("/home/tvt/workspace/favtuts/dummies");
obj.createDummyFiles().forEach(o -> obj.saveFile(path, o));
5. forEach vs forEachOrdered
5.1 The forEach does not guarantee the stream’s encounter order, regardless of whether the stream is sequential or parallel. The result is obvious when run in a parallel mode.
Stream<String> s = Stream.of("a", "b", "c", "1", "2", "3");
s.parallel().forEach(x -> System.out.println(x));
Each run will generate different result:
1
2
b
c
3
a
5.2 The forEachOrdered guarantees the stream’s encounter order; thus, it sacrifices the benefit of parallelism.
Stream<String> s = Stream.of("a", "b", "c", "1", "2", "3");
// keep order, it is always a,b,c,1,2,3
s.parallel().forEachOrdered(x -> System.out.println(x));
The result is always a,b,c,1,2,3
a
b
c
1
2
3
Download Source Code
$ git clone https://github.com/favtuts/java-core-tutorials-examples
$ cd java-basic/java8/misc