Collection.stream (). forEach ()와 Collection.forEach ()의 차이점은 무엇입니까?


286

를 사용하면 병렬 .stream()작업과 같은 체인 작업을 .filter()사용하거나 병렬 스트림을 사용할 수 있음을 이해 합니다. 그러나 작은 작업을 수행 해야하는 경우 (예 : 목록 요소 인쇄) 차이점은 무엇입니까?

collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);

답변:


287

설명 된 것과 같은 간단한 경우에는 대부분 동일합니다. 그러나 중요한 몇 가지 미묘한 차이점이 있습니다.

한 가지 문제는 주문과 관련이 있습니다. 를 사용 Stream.forEach하면 순서가 정의되지 않습니다 . 순차적 인 스트림에서는 발생하지 않지만 여전히 Stream.forEach임의의 순서로 실행 하기위한 사양 내에 있습니다. 이것은 병렬 스트림에서 자주 발생합니다. 대조적으로, 는 지정된 경우 Iterable.forEach항상 반복 순서대로 실행 Iterable됩니다.

또 다른 문제는 부작용입니다. 에 지정된 조치 Stream.forEach방해되지 않아야 합니다. ( java.util.stream 패키지 doc 참조 ) Iterable.forEach잠재적으로 제한이 적습니다. 에서 컬렉션의 경우 java.util, Iterable.forEach일반적으로 그 수집의 사용 Iterator설계되어 대부분의 것은 할 수 르파 하고있는이 발생합니다 ConcurrentModificationException컬렉션이 구조적으로 반복 중에 수정되는 경우. 그러나, 구조없는 변형 되는 반복 동안시켰다. 예를 들어, ArrayList 클래스 문서 는 "단지 요소의 값을 설정하는 것은 구조적인 수정이 아닙니다"라고 말합니다. 따라서ArrayList.forEachArrayList문제없이 기본 값을 설정할 수 있습니다.

동시 컬렉션은 아직 다릅니다. 빠른 실패 대신 약하게 일관성있게 설계되었습니다 . 전체 정의는 해당 링크에 있습니다. 그러나 간단히 생각해보십시오 ConcurrentLinkedDeque. 그 전달 동작 forEach방법은 있다 하더라도 구조적으로, 기본 양단 큐를 수정할 수 및 ConcurrentModificationException발생하지 않습니다. 그러나이 수정에서 발생하는 수정 내용이 표시되거나 표시되지 않을 수 있습니다. (따라서 "약한"일관성.)

Iterable.forEach동기화 된 컬렉션을 반복하는 경우 또 다른 차이점이 있습니다 . 이러한 콜렉션에서는 Iterable.forEach 콜렉션의 잠금을 한 번 수행하고 조치 메소드에 대한 모든 호출에서 보유합니다. 이 Stream.forEach호출은 컬렉션의 스플리터를 사용하며,이 스플리터는 잠기지 않으며 일반적인 비 간섭 규칙에 의존합니다. 스트림을 지원하는 콜렉션은 반복 중에 수정 될 수 있으며, 스트림 인 경우 ConcurrentModificationException일관성이없는 동작이 발생할 수 있습니다.


Iterable.forEach takes the collection's lock. 이 정보는 어디에서 왔습니까? JDK 소스에서 이러한 동작을 찾을 수 없습니다.
turbanoff


@Stuart, 비 간섭에 대해 자세히 설명해 주시겠습니까? Stream.forEach () 또한 ConcurrentModificationException (적어도 나를 위해)을 throw합니다.
yuranos

1
@ yuranos87와 같은 많은 컬렉션 ArrayList은 동시 수정을 상당히 엄격하게 검사하므로 종종 throw ConcurrentModificationException됩니다. 그러나 이것은 특히 병렬 스트림의 경우 보장되지 않습니다. CME 대신 예기치 않은 답변을 얻을 수 있습니다. 스트림 소스에 대한 비 구조적 수정도 고려하십시오. 병렬 스트림의 경우 특정 요소를 처리 할 스레드가 무엇인지, 수정 당시 처리되었는지 여부를 알 수 없습니다. 이것은 각 경기에서 다른 결과를 얻을 수 있고 CME를 얻지 못하는 경쟁 조건을 설정합니다.
스튜어트 마크

30

이 답변은 다양한 루프 구현의 성능과 관련이 있습니다. VERY OFTEN (수백만 건의 호출)이라고하는 루프와는 거의 관련이 없습니다. 대부분의 경우 루프의 내용은 가장 비싼 요소입니다. 루프를 자주 반복하는 상황에서는 여전히 관심이있을 수 있습니다.

구현에 따라 다르므로 ( 전체 소스 코드 ) 대상 시스템에서이 테스트를 반복해야합니다 .

빠른 Linux 시스템에서 openjdk 버전 1.8.0_111을 실행합니다.

integers(10 ^ 0-> 10 ^ 5 항목)에 대해 다양한 크기 의이 코드를 사용하여 List에서 10 ^ 6 회 반복되는 테스트를 작성했습니다 .

결과는 다음과 같습니다. 가장 빠른 방법은 목록의 항목 수에 따라 다릅니다.

그러나 여전히 최악의 상황에서는 10 ^ 6 회 이상 10 ^ 6 회 반복하는 것이 최악의 수행자에게는 100 초가 걸렸으므로 거의 모든 상황에서 다른 고려 사항이 더 중요합니다.

public int outside = 0;

private void forCounter(List<Integer> integers) {
    for(int ii = 0; ii < integers.size(); ii++) {
        Integer next = integers.get(ii);
        outside = next*next;
    }
}

private void forEach(List<Integer> integers) {
    for(Integer next : integers) {
        outside = next * next;
    }
}

private void iteratorForEach(List<Integer> integers) {
    integers.forEach((ii) -> {
        outside = ii*ii;
    });
}
private void iteratorStream(List<Integer> integers) {
    integers.stream().forEach((ii) -> {
        outside = ii*ii;
    });
}

여기 내 타이밍이 있습니다 : 밀리 초 / 기능 / 목록의 항목 수. 각 실행은 10 ^ 6 루프입니다.

                           1    10    100    1000    10000
       iterator.forEach   27   116    959    8832    88958
               for:each   53   171   1262   11164   111005
         for with index   39   112    920    8577    89212
iterable.stream.forEach  255   324   1030    8519    88419

실험을 반복하면 전체 소스 코드를 게시했습니다 . 이 답변을 편집하고 테스트 한 시스템 표기법으로 결과를 추가하십시오.


MacBook Pro, 2.5GHz Intel Core i7, 16GB, macOS 10.12.6 사용 :

                           1    10    100    1000    10000
       iterator.forEach   27   106   1047    8516    88044
               for:each   46   143   1182   10548   101925
         for with index   49   145    887    7614    81130
iterable.stream.forEach  393   397   1108    8908    88361

Java 8 핫스팟 VM-3.4GHz Intel Xeon, 8GB, Windows 10 Pro

                            1    10    100    1000    10000
        iterator.forEach   30   115    928    8384    85911
                for:each   40   125   1166   10804   108006
          for with index   30   120    956    8247    81116
 iterable.stream.forEach  260   237   1020    8401    84883

Java 11 핫스팟 VM-3.4GHz Intel Xeon, 8GB, Windows 10 Pro
(위와 동일한 시스템, 다른 JDK 버전)

                            1    10    100    1000    10000
        iterator.forEach   20   104    940    8350    88918
                for:each   50   140    991    8497    89873
          for with index   37   140    945    8646    90402
 iterable.stream.forEach  200   270   1054    8558    87449

Java 11 OpenJ9 VM-3.4GHz Intel Xeon, 8GB, Windows 10 Pro
(위와 동일한 머신 및 JDK 버전, 다른 VM)

                            1    10    100    1000    10000
        iterator.forEach  211   475   3499   33631   336108
                for:each  200   375   2793   27249   272590
          for with index  384   467   2718   26036   261408
 iterable.stream.forEach  515   714   3096   26320   262786

Java 8 핫스팟 VM-2.8GHz AMD, 64GB, Windows Server 2016

                            1    10    100    1000    10000
        iterator.forEach   95   192   2076   19269   198519
                for:each  157   224   2492   25466   248494
          for with index  140   368   2084   22294   207092
 iterable.stream.forEach  946   687   2206   21697   238457

Java 11 핫스팟 VM-2.8GHz AMD, 64GB, Windows Server 2016
(위와 동일한 시스템, 다른 JDK 버전)

                            1    10    100    1000    10000
        iterator.forEach   72   269   1972   23157   229445
                for:each  192   376   2114   24389   233544
          for with index  165   424   2123   20853   220356
 iterable.stream.forEach  921   660   2194   23840   204817

Java 11 OpenJ9 VM-2.8GHz AMD, 64GB, Windows Server 2016
(위와 동일한 머신 및 JDK 버전, 다른 VM)

                            1    10    100    1000    10000
        iterator.forEach  592   914   7232   59062   529497
                for:each  477  1576  14706  129724  1190001
          for with index  893   838   7265   74045   842927
 iterable.stream.forEach 1359  1782  11869  104427   958584

선택한 VM 구현은 또한 Hotspot / OpenJ9 / etc와 차별화됩니다.


3
정말 좋은 답변입니다. 감사합니다! 그러나 첫눈에 (그리고 두 번째에서) 어떤 방법이 어떤 실험에 해당되는지는 확실하지 않습니다.
torina

이 답변에는 코드 테스트에 더 많은 투표가 필요하다고 생각합니다. :).
Cory

테스트 사례 +1
Centos

8

당신이 언급 한 둘 사이에는 차이가 없습니다. 적어도 개념적으로 Collection.forEach()는 약칭입니다.

내부적으로 stream()버전은 객체 생성으로 인해 약간 더 많은 오버 헤드가 있지만 런타임을 보면 오버 헤드가 없습니다.

두 구현 모두 collection내용을 한 번 반복하고 반복 하는 동안 요소를 인쇄합니다.


언급 한 객체 생성 오버 헤드 Stream는 생성 중이거나 개별 객체 를 참조하고 있습니까? AFAIK, a Stream는 요소를 복제하지 않습니다.
Raffi Khatchadourian

30
이 답변은 Oracle Corporation에서 Java 코어 라이브러리를 개발하는 신사가 작성한 훌륭한 답변과 모순되는 것 같습니다.
Dawood ibn Kareem

0

Collection.forEach ()는 컬렉션의 반복자를 사용합니다 (지정된 경우). 이는 품목의 처리 순서가 정의되었음을 의미합니다. 반대로 Collection.stream (). forEach ()의 처리 순서는 정의되어 있지 않습니다.

대부분의 경우, 우리가 선택한 두 가지를 구별하지 않습니다. 병렬 스트림을 사용하면 여러 스레드에서 스트림을 실행할 수 있으며 이러한 상황에서는 실행 순서가 정의되지 않습니다. Java는 Collectors.toList ()와 같은 터미널 조작이 호출되기 전에 모든 스레드 만 완료하면됩니다. 컬렉션에서 직접 forEach ()를 직접 호출하고 병렬 스트림에서 두 번째로 호출하는 예를 살펴 보겠습니다.

list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);

코드를 여러 번 실행하면 list.forEach ()가 항목을 삽입 순서대로 처리하는 반면 list.parallelStream (). forEach ()는 각 실행에서 다른 결과를 생성합니다. 가능한 출력은 다음과 같습니다.

ABCD CDBA

다른 하나는 다음과 같습니다.

ABCD DBCA
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.