In Java 8, we can use the new forEach to loop or iterate a MapListSet, 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

References

Leave a Reply

Your email address will not be published. Required fields are marked *