Study/java

[Java] Stream - forEach

 

Java 8부터 도입된 메소드로, for문이나 향상된 for문처럼 반복할 수 있는 방법 중 하나입니다.

 

Stream.forEach, Collection.forEach의 차이

forEach 메소드는 Stream에도 있고, Collection에도 존재합니다.

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

System.out.print("Collection.forEach: ");
list.forEach(num -> System.out.print(num + " "));

System.out.println("\\n=========================");

System.out.print("Stream.forEach: ");
list.stream().forEach(num -> System.out.print(num + " "));

Collection.forEach, Stream.forEach 사용 결과

스트림의 forEach와 컬렉션의 forEach 둘다 Consumer를 받아서 반복을 진행하는 것은 동일합니다.

컬렉션의 forEach는 내부에서 Iterable 객체를 사용합니다.

그리고 스트림의 forEach는 스트림 객체를 사용합니다.

 

list.stream().forEach를 하면 리스트에서 스트림 객체를 생성하고 그 스트림에 있는 forEach 메소드를 사용하게 됩니다.

그리고 컬렉션의 forEach는 컬렉션 내부에 있는 Iterator를 사용하기 때문에 순서가 보장이 됩니다.

 

하지만 스트림은 보장이 되지 않을 수도 있습니다.

list.parallelStream().forEach(num -> System.out.print(num + " "));

parallelStream.forEach 사용 결과

위의 예제에서 stream()을 parallelStream()으로 변경하니 순서가 다르게 출력되었습니다.

순서가 보장되지 않음을 보여주기 위해 parallelStream을 사용했지만, stream도 순서가 보장되지 않습니다.

stream을 사용했을 때는 순서대로 나온게 아니라 그냥 요소를 가져온건데 우연히 순서가 맞게 나온 것입니다.

parallelStream의 경우 여러 스레드에서 스트림을 실행할 수 있기 때문에 실행 순서를 정의하지 않고 있습니다.

그래서 순서가 섞였습니다.

 

forEachOrdered

스트림의 순서를 보장하고 싶다면 forEachOrdered를 사용하면 됩니다.

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

System.out.print("parallelStream.forEachOrdered : ");
list.parallelStream().forEachOrdered(num -> System.out.print(num + " "));
System.out.print("\\nparallelStream.forEach : ");
list.parallelStream().forEach(num -> System.out.print(num + " "));

parallelStream.forEachOrdered 사용 결과

주의사항

Stream의 forEach를 사용할 때는 객체를 수정하면 안됩니다.

컬렉션의 forEach는 내부에서 향상된 for문을 사용하기 때문에 반복 도중에 수정을 하게 되면 ConcurrentModificationException이 발생합니다.

list.forEach(num -> {
    System.out.println(num);
    if (num == 2) {
        list.remove(num);
    }
});

Collection.forEach에서 컬렉션 수정 시 ConcurrentModificationException 발생

1, 2를 출력하고 2를 제거하고 3을 돌기 전에 수정 여부 체크를 통과하지 못하여 ConcurrentModificationException이 발생했습니다.

 

스트림으로 변경하면 다음과 같습니다.

list.stream().forEach(num -> {
    System.out.println(num);
    if (num == 2) {
        list.remove(num);
    }
});

Stream.forEach에서 컬렉션 수정 시 발생하는 예외

1, 2를 출력하고 2를 제거해서 3 순서가 되었을 때 null을 출력하고 NullPointerException이 발생했습니다.

요소를 제거해서 한칸씩 앞으로 당겨져서 원래 3이 있던 위치에 아무것도 없게 되어 예외가 발생한 것 같습니다.

 

컬렉션의 forEach와 스트림의 forEach는 비슷하지만 용도가 다릅니다.

스트림은 병렬 작업에 특화되어 단일 스레드에서 사용하는 경우 비효율적일 수 있습니다.