To loop or iterate a Map, List, Set, or Stream in Java 8, we can use the new forEach method.

Loop a Map

1,1 A standard method for looping a Map is shown below.

 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 We can use forEach in Java 8 to loop through 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 The forEach will print null if the Map’s key or value includes 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 standard way of looping a Map produces the same results as the above.

1.4 Add a quick null check within the forEach if we don’t want to print the null key.

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

Loop a List

2.1 Here’s how to loop a List normally.

  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);
      }

  }

To loop a List in Java 8, use the forEach process.

 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 a List’s null value.

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 functions for Set and Stream are similar.

forEach and Consumer

3.1 Examine the signature of the forEach method; it recognizes a functional interface User.


public interface Iterable<T> {

  default void forEach(Consumer<? super T> action) {
      Objects.requireNonNull(action);
      for (T t : this) {
          action.accept(t);
      }
  }
  //..
}
public interface Stream<T> extends BaseStream<T, Stream<T>> {

  void forEach(Consumer<? super T> action);
  //...

}

3.2 In this case, a Consumer method is created to print a String in Hex format. We can now reuse the User method and move it to List and Stream’s forEach methods.

import java.util.*;
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);

        stream.forEach(printTextInHexConsumer);

    }

}

Output


abc       :616263
java      :6a617661
python    :707974686f6e

abc       :616263
java      :6a617661
python    :707974686f6e

forEach and Exception handling.

4.1 The forEach method isn’t just for printing; this example demonstrates how to use it to loop through a list of objects and save the results to files.


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("C:\\test"), 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.

C:\\test\.txt
hello

C:\\test\.txt
world

C:\\test\.txt
java

4.2 The Records Since write can throw an IOException, we must catch it within the forEach loop; as a result, the code is ugly. Extracting the code to a new method is a standard procedure.

 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("C:\\test");
  obj.createDummyFiles().forEach(o -> obj.saveFile(path, o));

forEach vs forEachOrdered

5.1 Regardless of whether the stream is sequential or parallel, the forEach does not guarantee the stream’s encounter order. When run in parallel, the result is evident.

  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

References

Laisser un commentaire