Java 8 Iterable.forEach () 대 foreach 루프


465

다음 중 Java 8에서 더 나은 방법은 무엇입니까?

자바 8 :

joins.forEach(join -> mIrc.join(mSession, join));

자바 7 :

for (String join : joins) {
    mIrc.join(mSession, join);
}

람다로 "단순화"될 수있는 많은 for 루프가 있지만 실제로 사용하면 어떤 이점이 있습니까? 성능과 가독성이 향상됩니까?

편집하다

또한이 질문을 더 긴 방법으로 확장 할 것입니다. 부모 함수를 람다에서 반환하거나 중단 할 수 없으며 이것을 비교할 때 고려해야 할 사항이 있지만 고려해야 할 다른 것이 있습니까?


10
서로에 대한 실제 성능 이점은 없습니다. 첫 번째 옵션은 FP에서 영감을 얻은 것입니다 (휘트는 일반적으로 코드를 표현하는 "좋은"방법과 "명확한"방법에 대해 이야기합니다). 실제로-이것은 "스타일"질문입니다.
유진 로이

5
@Dwb :이 경우에는 관련이 없습니다. forEach는 병렬 또는 이와 유사한 것으로 정의되지 않으므로이 두 가지는 의미 적으로 동일합니다. 물론 forEach의 병렬 버전을 구현할 수 있으며 (이미 표준 라이브러리에 이미있을 수도 있음) 이러한 경우 람다 식 구문이 매우 유용합니다.
AardvarkSoup

8
@AardvarkSoup forEach가 호출되는 인스턴스는 스트림 ( lambdadoc.net/api/java/util/stream/Stream.html )입니다. 병렬 실행을 요청하려면 joins.parallel (). forEach (...)를 작성할 수 있습니다.
mschenk74

13
인가 joins.forEach((join) -> mIrc.join(mSession, join));의 "단순화"정말 for (String join : joins) { mIrc.join(mSession, join); }? 의 유형을 숨길 수 있도록 구두점 수를 9 개에서 12 개로 늘 렸습니다 join. 실제로 한 일은 두 줄의 진술을 한 줄에 넣는 것입니다.
Tom Hawtin-tackline

7
고려해야 할 또 다른 사항은 Java의 제한된 변수 캡처 기능입니다. Stream.forEach ()를 사용하면 로컬 변수를 캡처하여 최종 변수로 만들 수 없으므로 forEach 람다에서 상태 저장 동작을 수행 할 수 있습니다 (클래스 상태 변수 사용과 같은 추악한 경우가 아니라면).
RedGlyph

답변:


511

더 나은 방법은을 사용하는 것 for-each입니다. 신기한 신기한 원칙 인 Keep It Simple을 위반하는 것 외에도 forEach()최소한 다음과 같은 결함이 있습니다.

  • 최종 변수가 아닌 변수를 사용할 수 없습니다 . 따라서 다음과 같은 코드는 forEach 람다로 변환 할 수 없습니다.

    Object prev = null;
    for(Object curr : list)
    {
        if( prev != null )
            foo(prev, curr);
        prev = curr;
    }
  • 확인 된 예외를 처리 할 수 ​​없습니다 . 람다는 실제로 확인 된 예외를 던지는 것을 금지하지 않지만 일반적인 기능 인터페이스 Consumer는 선언하지 않습니다. 따라서 확인 된 예외를 발생시키는 모든 코드는 try-catch또는로 감싸 야합니다 Throwables.propagate(). 그러나 그렇게해도, 예외가 어떻게 발생하는지 항상 명확하지는 않습니다. 그것은 내장의 어딘가에 삼킬 수 있습니다forEach()

  • 제한된 흐름 제어 . return람다의 A continue는 for-each의 a와 같지만 a와 동등한 것은 없습니다 break. 반환 값, 단락 또는 설정 플래그 ( 최종 변수가 아닌 변수 규칙을 위반하지 않으면 약간 완화 할 수 있음)와 같은 작업을 수행하는 것도 어렵습니다 . "이것은 최적화 일뿐만 아니라 파일에서 행을 읽는 것과 같은 일부 시퀀스에 부작용이 있거나 무한 시퀀스가있을 수 있다고 생각할 때 중요합니다."

  • 병렬로 실행될 수 있습니다. 이는 최적화해야 할 코드의 0.1 %를 제외한 모든 사람에게 끔찍하고 끔찍한 일입니다. 모든 병렬 코드는 잠금, 휘발성 및 전통적인 멀티 스레드 실행의 다른 불쾌한 측면을 사용하지 않더라도 고려해야합니다. 모든 버그를 찾기가 어렵습니다.

  • JIT가 일반 루프와 같은 정도로 forEach () + lambda를 최적화 할 수 없으므로 특히 람다는 새롭기 때문에 성능이 저하 될 수 있습니다. "최적화"란 람다 (작은)를 호출하는 오버 헤드가 아니라 현대 JIT 컴파일러가 코드 실행시 수행하는 정교한 분석 및 변환을 의미합니다.

  • 병렬 처리가 필요한 경우 ExecutorService를 사용하는 것이 훨씬 빠르며 훨씬 어렵지 않습니다 . (: 많은 문제에 대해 모르는 읽기) 스트림은 모두 자동적 인은 전문 (읽기 : 일반적인 경우 비효율적)를 사용하는 병렬 전략을 ( 재귀 분해 포크 - 조인 ).

  • 중첩 된 호출 계층 구조 및 병렬 실행으로 인해 디버깅이 더 혼동 됩니다. 디버거에 주변 코드의 변수를 표시하는 데 문제가있을 수 있으며 단계별와 같은 기능이 예상대로 작동하지 않을 수 있습니다.

  • 스트림은 일반적으로 코딩, 읽기 및 디버그가 더 어렵습니다 . 실제로 이것은 일반적으로 복잡한 " 유창한 "API에 해당됩니다. 복잡한 단일 명령문의 조합, 제네릭의 과도한 사용 및 중간 변수의 부족으로 인해 혼란스러운 오류 메시지가 생성되고 디버깅이 좌절됩니다. "이 방법에는 X 형에 대한 과부하가 없습니다"대신 "유형을 엉망으로 만든 곳이 어디인지, 어떻게 알 수 있는지"라는 오류 메시지가 나타납니다. 마찬가지로 코드가 여러 명령문으로 분리되고 중간 값이 변수에 저장 될 때처럼 쉽게 디버거에서 작업을 수행하고 조사 할 수 없습니다. 마지막으로, 코드를 읽고 각 실행 단계에서의 유형과 동작을 이해하는 것은 쉽지 않을 수 있습니다.

  • 아픈 엄지 손가락처럼 튀어 나옵니다 . Java 언어에는 이미 for-each 문이 있습니다. 함수 호출로 대체해야하는 이유는 무엇입니까? 표현의 어딘가에 부작용을 숨기도록 권장하는 이유는 무엇입니까? 다루기 힘든 원 라이너를 장려하는 이유는 무엇입니까? 규칙적인 for-each와 new forEach willy-nilly를 혼합하는 것은 나쁜 스타일입니다. 코드는 관용구 (반복으로 인해 빠르게 이해되는 패턴)로 말해야하며, 관용구가 적을수록 코드가 더 명확 해지고 사용되는 관용구를 결정하는 데 소요되는 시간이 줄어 듭니다 (나 같은 완벽 주의자들에게는 큰 시간 낭비)! ).

보시다시피, 나는 의미가있는 경우를 제외하고는 forEach ()의 큰 팬이 아닙니다.

특히 나에게 불쾌한 Stream것은 구현하지 않고 Iterable(실제로 method가 있음에도 불구하고 iterator) forEach ()만으로 for-each에서 사용할 수 없다는 사실 입니다. 를 사용하여 스트림을 Iterables로 캐스팅하는 것이 좋습니다 (Iterable<T>)stream::iterator. 더 나은 대안은 구현을 포함하여 많은 Stream API 문제를 해결하는 StreamEx 를 사용 하는 것 Iterable입니다.

즉, forEach()다음에 유용합니다.

  • 동기화 된 목록을 원자 적으로 반복합니다 . 이전에는 Collections.synchronizedList()get 또는 set과 관련하여 생성 된 목록 이 원자 목록 이지만 반복 할 때 스레드로부터 안전하지 않았습니다.

  • 병렬 실행 (적절한 병렬 스트림 사용) . 이렇게하면 문제가 Streams and Spliterators에 내장 된 성능 가정과 일치하는 경우 ExecutorService를 사용하여 몇 줄의 코드를 절약 할 수 있습니다.

  • 동기화 된 목록과 같이 반복 제어를 통해 이익을 얻는 특정 컨테이너 (사람들이 더 많은 예제를 제시 할 수 없다면 이것은 이론적 인 것이지만)

  • forEach()및 메소드 참조 인수 (예 :) 를 사용하여 단일 함수를보다 명확하게 호출합니다list.forEach (obj::someMethod) . 그러나 확인 된 예외, 더 어려운 디버깅 및 코드 작성시 사용하는 관용구 수 감소에 대한 요점을 명심하십시오.

내가 참조로 사용한 기사 :

편집 : 람다에 대한 원래 제안 중 일부 (예 : http://www.javac.info/closures-v06a.html )는 내가 언급 한 문제 중 일부를 해결 한 것처럼 보입니다 (물론 자체 합병증을 추가하는 동안).


95
"어딘가 표정에 부작용을 숨기는 것이 좋은 이유는 무엇입니까?" 잘못된 질문입니다. 기능 forEach은 기능적 스타일을 장려하기 위해, 즉 부작용 없이 표현 사용하는 것입니다. 상황이 발생하면 forEach부작용으로 잘 작동하지 않습니다. 작업에 적합한 도구를 사용하지 않는다는 느낌이들 것입니다. 그렇다면 간단한 대답은 그것이 당신의 느낌이 옳기 때문입니다. 그래서 for-each 루프를 유지하십시오. 클래식 for루프는 더 이상 사용되지 않습니다…
Holger

17
@Holger forEach부작용없이 어떻게 사용할 수 있습니까?
Aleksandr Dubinsky

14
나는 충분히 정확하지 forEach않았지만 부작용을위한 유일한 스트림 작업이지만 예제 코드와 같은 부작용은 아닙니다 reduce. 카운팅은 일반적인 작업입니다. 나는 쿵쿵 거리는 소리로 지역 변수를 조작하거나 제어 for루프 (예외 처리 포함)에 영향을 미치는 모든 작업을 클래식 루프 에서 유지하는 것이 좋습니다. 내가 생각하는 원래 질문과 관련하여 문제는 누군가가 스트림 for소스를 통한 간단한 루프로 충분한 스트림을 사용한다는 사실에서 비롯됩니다 . forEach()작동 하는 스트림 만 사용
Holger

8
@Holger forEach적절한 부작용의 예는 무엇입니까 ?
Aleksandr Dubinsky

29
각 항목을 개별적으로 처리하고 로컬 변수를 변경하지 않는 것. 예를 들어, 항목 자체를 조작하거나 인쇄하거나, 파일, 네트워크 스트림 등에 쓰기 / 보내기 등을들 수 있습니다.이 예제에 의문을 제기하고 해당 응용 프로그램이 보이지 않는다면 아무 문제가 없습니다. 필터링, 매핑, 축소, 검색 및 (더 낮은 정도로) 수집은 스트림의 기본 작업입니다. forEach는 기존 API와 연결하기에 편리한 것 같습니다. 물론 병렬 작업의 경우. for루프 에서는 작동하지 않습니다 .
Holger

169

작업을 병렬로 실행할 수있는 경우 이점이 고려됩니다. ( http://java.dzone.com/articles/devoxx-2012-java-8-lambda-and- 내부 및 외부 반복에 대한 섹션 참조 )

  • 필자의 관점에서 가장 큰 장점은 루프 내에서 수행 할 구현을 병렬 또는 순차적으로 실행할지 여부를 결정할 필요없이 정의 할 수 있다는 것입니다.

  • 루프를 병렬로 실행하려면 간단히 쓸 수 있습니다.

     joins.parallelStream().forEach(join -> mIrc.join(mSession, join));

    스레드 처리 등을위한 추가 코드를 작성해야합니다.

참고 : 내 대답으로는 java.util.Stream인터페이스 구현에 참여한다고 가정했습니다 . 조인 java.util.Iterable이 인터페이스 만 구현하면 더 이상 사실이 아닙니다.


4
그가 참조하고있는 오라클 엔지니어의 슬라이드 ( blogs.oracle.com/darcy/resource/Devoxx/… )는 이러한 람다 식 내에서의 병렬성을 언급하지 않습니다. 병렬 처리는 map& 와 같은 대량 수집 방법 내에서 발생할 수 있으며 fold실제로 람다와 관련이 없습니다.
Thomas Jungblut

1
OP의 코드가 여기서 자동 병렬 처리의 이점을 얻는 것 같지는 않습니다 (특히 하나가있을 것이라는 보장이 없기 때문에). 우리는 "mIrc"가 무엇인지 모르지만 "join"은 실제로 순서가 잘못 될 수있는 것처럼 보이지 않습니다.
Eugene Loy 2014 년

11
Stream#forEachIterable#forEach같은 것이 아니다. OP가 문의하고 Iterable#forEach있습니다.
gvlasov

2
질문이 제기 된 시간과 답변이 업데이트 된 시간 사이에 사양이 변경되었으므로 UPDATEX 스타일을 사용했습니다. 대답의 역사가 없으면 더 혼란 스러울 것입니다.
mschenk74

1
누구든지 대신 joins구현 Iterable하는 경우이 대답이 유효하지 않은 이유를 설명해 주 Stream시겠습니까? 내가 읽은 몇 가지에서, 영업 이익은 할 수 있어야 joins.stream().forEach((join) -> mIrc.join(mSession, join));하고 joins.parallelStream().forEach((join) -> mIrc.join(mSession, join));만약 joins구현Iterable
Blueriver

112

이 질문을 읽을 때 Iterable#forEach람다 식과 함께 전통적인 for-each 루프를 작성하는 지름길 / 대체 라는 인상을 얻을 수 있습니다 . 이것은 단순히 사실이 아닙니다. OP 의이 코드 :

joins.forEach(join -> mIrc.join(mSession, join));

작문의 지름길 이 아닙니다

for (String join : joins) {
    mIrc.join(mSession, join);
}

확실히 이런 식으로 사용해서는 안됩니다. 대신 쓰기를위한 지름길 ( 정확히 같지는 않지만 )

joins.forEach(new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
});

그리고 다음 Java 7 코드를 대체합니다.

final Consumer<T> c = new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
};
for (T t : joins) {
    c.accept(t);
}

위의 예와 같이 루프 본문을 기능적 인터페이스로 바꾸면 코드가보다 명확 해집니다. (1) 루프 본문은 주변 코드 및 제어 흐름에 영향을 미치지 않으며 (2) 루프의 본문은 주변 코드에 영향을주지 않으면 서 다른 기능 구현으로 대체 될 수 있습니다. 외부 스코프의 비 최종 변수에 액세스 할 수없는 것은 함수 / 람다의 결함이 아니며 , 전통적인 for-each 루프의 의미와 의미를 구별 하는 기능 입니다 Iterable#forEach. 의 구문에 Iterable#forEach익숙해지면 코드에 대한 추가 정보를 즉시 얻으므로 코드를 더 읽기 쉽게 만듭니다.

전통적인 for-each 루프는 자바에서 좋은 습관을 유지할 것입니다 (과도한 용어 " 모범 사례 " 를 피하기 위해 ). 그러나 이것이 Iterable#forEach나쁜 습관이나 나쁜 스타일로 간주되어야 한다는 것을 의미하지는 않습니다 . 작업을 수행하기 위해 올바른 도구를 사용하는 것은 항상 모범 사례이며, 여기에는 전통적인 for-each 루프와을 혼합하는 것이 포함됩니다 Iterable#forEach.

Iterable#forEach이 스레드에서 이미 단점을 논의 했으므로 다음과 같은 이유가 있습니다 Iterable#forEach.

  • 코드가 더 명확하게하려면 다음과 같이, 위에서 설명한 Iterable#forEach 수 있습니다 어떤 상황에서 당신의 코드가 더 명확하고 쉽게 읽을 수 있도록.

  • 코드를보다 확장 가능하고 유지 보수 가능하게하려면 : 함수를 루프 본문으로 사용하면이 기능을 다른 구현으로 대체 할 수 있습니다 ( 전략 패턴 참조 ). 예를 들어 람다 식을 메서드 호출로 쉽게 대체 할 수 있습니다.이 메서드는 하위 클래스로 덮어 쓸 수 있습니다.

    joins.forEach(getJoinStrategy());

    그런 다음 기능 인터페이스를 구현하는 열거 형을 사용하여 기본 전략을 제공 할 수 있습니다. 이를 통해 코드를 확장 할 수있을뿐만 아니라 루프 구현에서 루프 구현을 분리하므로 유지 관리 성이 향상됩니다.

  • 코드를 더 디버깅 가능하게 만들려면 : 선언에서 루프 구현을 분리하면 디버깅이 더 쉬워 질 수 있습니다. 특수 디버그 구현을 통해 메인 코드를 복잡하게 만들 필요없이 디버그 메시지를 인쇄 할 수 있기 때문입니다 if(DEBUG)System.out.println(). 디버그 구현은 실제 함수 구현 을 장식 하는 delegate 일 수 있습니다 .

  • 최적화 성능이 중요한 코드 : 어설의 일부는 반대로이 스레드, Iterable#forEach 않습니다 이미보다 더 나은 성능을 제공하는 전통에 대한-각 루프, 최소한의 ArrayList를 사용하여 "-client"모드에서 핫스팟을 실행할 때. 이 성능 향상은 작고 대부분의 사용 사례에서 무시할 만하지 만이 추가 성능으로 인해 차이가 발생할 수 있습니다. 예를 들어 라이브러리 유지 관리자는 기존 루프 구현 중 일부를로 대체해야하는 경우 확실히 평가하려고합니다 Iterable#forEach.

    이 진술을 사실로 뒷받침하기 위해 Caliper를 사용 하여 마이크로 벤치 마크를 수행했습니다 . 테스트 코드는 다음과 같습니다 (git의 최신 Caliper가 필요합니다).

    @VmOptions("-server")
    public class Java8IterationBenchmarks {
    
        public static class TestObject {
            public int result;
        }
    
        public @Param({"100", "10000"}) int elementCount;
    
        ArrayList<TestObject> list;
        TestObject[] array;
    
        @BeforeExperiment
        public void setup(){
            list = new ArrayList<>(elementCount);
            for (int i = 0; i < elementCount; i++) {
                list.add(new TestObject());
            }
            array = list.toArray(new TestObject[list.size()]);
        }
    
        @Benchmark
        public void timeTraditionalForEach(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : list) {
                    t.result++;
                }
            }
            return;
        }
    
        @Benchmark
        public void timeForEachAnonymousClass(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(new Consumer<TestObject>() {
                    @Override
                    public void accept(TestObject t) {
                        t.result++;
                    }
                });
            }
            return;
        }
    
        @Benchmark
        public void timeForEachLambda(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(t -> t.result++);
            }
            return;
        }
    
        @Benchmark
        public void timeForEachOverArray(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : array) {
                    t.result++;
                }
            }
        }
    }

    결과는 다음과 같습니다.

    "-client"로 실행 Iterable#forEach하면 ArrayList보다 전통적인 for 루프보다 성능이 뛰어나지 만 어레이를 직접 반복하는 것보다 여전히 느립니다. "-server"로 실행할 때 모든 접근 방식의 성능은 거의 같습니다.

  • 병렬 실행에 대한 선택적 지원을 제공하려면 다음과 같이 이미 스트림을Iterable#forEach 사용하여 기능 인터페이스를 병렬 로 실행할 수있는 가능성이 중요한 측면이라고합니다. 루프가 실제로 병렬로 실행되는 것을 보장하지는 않기 때문에 이를 선택적 기능으로 고려해야 합니다. 를 사용하여 목록을 반복하면 다음과 같이 명시 적으로 말할 수 있습니다.이 루프 병렬 실행을 지원 하지만 그에 의존하지는 않습니다. 다시 말하지만 이것은 기능이며 결함이 아닙니다!Collection#parallelStream()list.parallelStream().forEach(...);

    병렬 실행에 대한 의사 결정을 실제 루프 구현에서 멀어지게하면 코드 자체에 영향을주지 않고 코드를 선택적으로 최적화 할 수 있습니다. 또한 기본 병렬 스트림 구현이 요구 사항에 맞지 않으면 아무도 자신의 구현을 제공하지 못합니다. 예를 들어 기본 운영 체제, 컬렉션 크기, 코어 수 및 일부 기본 설정에 따라 최적화 된 컬렉션을 제공 할 수 있습니다.

    public abstract class MyOptimizedCollection<E> implements Collection<E>{
        private enum OperatingSystem{
            LINUX, WINDOWS, ANDROID
        }
        private OperatingSystem operatingSystem = OperatingSystem.WINDOWS;
        private int numberOfCores = Runtime.getRuntime().availableProcessors();
        private Collection<E> delegate;
    
        @Override
        public Stream<E> parallelStream() {
            if (!System.getProperty("parallelSupport").equals("true")) {
                return this.delegate.stream();
            }
            switch (operatingSystem) {
                case WINDOWS:
                    if (numberOfCores > 3 && delegate.size() > 10000) {
                        return this.delegate.parallelStream();
                    }else{
                        return this.delegate.stream();
                    }
                case LINUX:
                    return SomeVerySpecialStreamImplementation.stream(this.delegate.spliterator());
                case ANDROID:
                default:
                    return this.delegate.stream();
            }
        }
    }

    여기서 좋은 점은 루프 구현이 이러한 세부 사항을 알거나 신경 쓸 필요가 없다는 것입니다.


5
이 토론에서 흥미로운 견해를 가지고 있으며 여러 가지 요점을 제시합니다. 나는 그들을 해결하려고 노력할 것이다. 루프 바디의 특성에 관한 몇 가지 기준 을 전환 forEach하고 for-each그 기준에 따라 제안합니다 . 그러한 규칙을 따르는 지혜와 훈련은 훌륭한 프로그래머의 특징입니다. 주변 사람들이 규칙을 따르지 않거나 동의하지 않기 때문에 그러한 규칙도 그의 골칫거리입니다. 예를 들어, 확인 된 예외와 확인되지 않은 예외를 사용합니다. 이 상황은 훨씬 더 미묘한 것처럼 보입니다. 그러나 본체가 "서라운드 코드 또는 흐름 제어에 영향을 미치지 않는 경우"라면 더 나은 기능으로 고려하지 않습니까?
Aleksandr Dubinsky

4
자세한 설명 감사합니다 Aleksandr. But, if the body "does not affect surround code or flow control," isn't factoring it out as a function better?. 그렇습니다. 이것은 종종 제 생각에 해당 될 것입니다. 함수로서 이러한 루프를 고려하는 것은 자연스러운 결과입니다.
Balder

2
성능 문제와 관련하여 루프의 특성에 크게 의존한다고 생각합니다. 내가 작업중 인 프로젝트 Iterable#forEach에서 성능 향상으로 인해 Java 8 이전 과 비슷한 함수 스타일 루프를 사용하고 있습니다 . 해당 프로젝트에는 게임 루프와 유사한 하나의 메인 루프가 있으며, 정의되지 않은 수의 중첩 된 서브 루프가 있으며 클라이언트가 루프 참가자를 함수로 플러그인 할 수 있습니다. 이러한 소프트웨어 구조는 다음과 같은 이점을 제공 Iteable#forEach합니다.
Balder

7
내 비판의 끝에는 "코드가 관용구로 말해야하고, 관용구가 적을수록 코드가 더 명확하고 사용되는 관용구를 결정하는 데 소요되는 시간이 줄어 듭니다"라는 문장이 있습니다. C #에서 Java로 전환했을 때이 점에 깊이 감사하기 시작했습니다.
Aleksandr Dubinsky

7
그것은 나쁜 주장입니다. while 루프가 충분하고 관용구가 적기 때문에 for 루프를 사용하지 않아야하는 이유는 무엇이든 정당화하는 데 사용할 수 있습니다. 왜 goto가 그 이상을 할 수있을 때 루프, 스위치 또는 try / catch 문을 사용해야합니까?
tapichu

13

forEach()iterable은 표준 반복자 방식과 달리 요소를 반복하는 가장 좋은 방법을 알고 있기 때문에 for-each 루프보다 빠르게 구현할 수 있습니다. 차이점은 내부 루프 또는 외부 루프입니다.

예를 들어 다음 ArrayList.forEach(action)과 같이 간단하게 구현할 수 있습니다.

for(int i=0; i<size; i++)
    action.accept(elements[i])

많은 비계가 필요한 for-each 루프와 달리

Iterator iter = list.iterator();
while(iter.hasNext())
    Object next = iter.next();
    do something with `next`

그러나을 사용하여 두 가지 오버 헤드 비용을 고려해야합니다 forEach(). 하나는 람다 객체를 만들고 다른 하나는 람다 메서드를 호출하는 것입니다. 그들은 아마도 중요하지 않습니다.

다양한 사용 사례에 대한 내부 / 외부 반복을 비교하려면 http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/ 을 참조하십시오 .


8
iterable이 가장 좋은 방법을 알고 있지만 iterator는 모르는 이유는 무엇입니까?
mschenk74

2
본질적인 차이는 없지만 반복자 인터페이스를 준수하기 위해서는 추가 코드가 필요하므로 비용이 많이 듭니다.
ZhongYu

1
@ zhong.j.yu Collection을 구현하면 어쨌든 Iterable도 구현합니다. 따라서 "결함이있는 인터페이스 메소드를 구현하기 위해 더 많은 코드를 추가"라는 관점에서 코드 오버 헤드가 없습니다. mschenk74가 말했듯이 컬렉션을 가장 좋은 방법으로 반복하는 방법을 알기 위해 반복자를 조정할 수없는 이유는 없습니다. 나는 반복자 생성에 대한 오버 헤드가있을 수 있지만, 일반적으로 비용이 너무 저렴하여 비용이 전혀 들지 않는다는 것에 동의합니다.
Eugene Loy

4
예를 들어 트리 반복 : void forEach(Consumer<T> v){leftTree.forEach(v);v.accept(rootElem);rightTree.forEach(v);}, 이것은 외부 반복보다 더 우아하며, 최상의 동기화 방법을 결정할 수 있습니다
ratchet freak

1
재미있게도, String.join메소드 에서 유일한 주석 (좋아요, 잘못된 조인)은 "Arrays.stream 오버 헤드에 해당하지 않는 요소의 수"입니다. 그래서 그들은 포쉬 for 루프를 사용합니다.
Tom Hawtin-tackline

7

TL; DR : List.stream().forEach()가장 빠릅니다.

벤치마킹 반복의 결과를 추가해야한다고 생각했습니다. 벤치마킹 프레임 워크가없는 매우 간단한 접근 방식을 취하고 5 가지 방법을 벤치마킹했습니다.

  1. 권위 있는 for
  2. 클래식 foreach
  3. List.forEach()
  4. List.stream().forEach()
  5. List.parallelStream().forEach

테스트 절차 및 매개 변수

private List<Integer> list;
private final int size = 1_000_000;

public MyClass(){
    list = new ArrayList<>();
    Random rand = new Random();
    for (int i = 0; i < size; ++i) {
        list.add(rand.nextInt(size * 50));
    }    
}
private void doIt(Integer i) {
    i *= 2; //so it won't get JITed out
}

이 클래스의 목록은 반복되며 doIt(Integer i)매번 다른 방법을 통해 모든 멤버에게 적용됩니다. Main 클래스에서 테스트 된 메소드를 세 번 실행하여 JVM을 예열합니다. 그런 다음 각 반복 방법에 걸리는 시간을 합산하여 테스트 방법을 1000 번 실행합니다 ( System.nanoTime()). 그 후에 나는 그 합계를 1000으로 나눕니다. 그것이 결과, 평균 시간입니다. 예:

myClass.fored();
myClass.fored();
myClass.fored();
for (int i = 0; i < reps; ++i) {
    begin = System.nanoTime();
    myClass.fored();
    end = System.nanoTime();
    nanoSum += end - begin;
}
System.out.println(nanoSum / reps);

Java 버전 1.8.0_05의 i5 4 코어 CPU에서 이것을 실행했습니다.

권위 있는 for

for(int i = 0, l = list.size(); i < l; ++i) {
    doIt(list.get(i));
}

실행 시간 : 4.21ms

클래식 foreach

for(Integer i : list) {
    doIt(i);
}

실행 시간 : 5.95ms

List.forEach()

list.forEach((i) -> doIt(i));

실행 시간 : 3.11ms

List.stream().forEach()

list.stream().forEach((i) -> doIt(i));

실행 시간 : 2.79ms

List.parallelStream().forEach

list.parallelStream().forEach((i) -> doIt(i));

실행 시간 : 3.6ms


23
그 번호는 어떻게 얻습니까? 어떤 벤치 마크 프레임 워크를 사용하고 있습니까? 아무것도 사용하지 System.out.println않고이 데이터를 순진하게 표시하기 위해 평범한 경우 모든 결과가 쓸모가 없습니다.
Luiggi Mendoza

2
프레임 워크가 없습니다. 사용 System.nanoTime()합니다. 답을 읽으면 어떻게 수행되었는지 알 수 있습니다. 나는 이것이 상대적 질문 이므로 쓸모없는 것으로 생각하지 않습니다 . 나는 특정 방법이 얼마나 잘 수행되었는지 상관하지 않고 다른 방법과 비교하여 얼마나 잘 수행했는지에 관심이 있습니다.
Assaf

31
이것이 훌륭한 마이크로 벤치 마크의 목적입니다. 이러한 요구 사항을 충족하지 않았으므로 결과는 쓸모가 없습니다.
Luiggi Mendoza

6
대신 JMH를 알게되는 것이 좋습니다. 이것은 Java 자체에 사용되고 있으며 올바른 숫자를 얻기 위해 많은 노력을 기울입니다. openjdk.java.net/projects/code-tools/jmh
dsvensson

1
@LuiggiMendoza에 동의합니다. 이러한 결과가 일관되거나 유효하다는 것을 알 수있는 방법이 없습니다. 하나님은 내가 반복 한 벤치 마크, 크기 및 그렇지 않은 것에 따라 다른 결과를 계속보고하는 벤치 마크의 수를 알고 있습니다.
mmm

6

내 의견을 조금 확장해야한다고 생각합니다 ...

패러다임에 대하여

아마도 가장 눈에 띄는 부분 일 것입니다. FP는 부작용을 피할 수 있기 때문에 인기를 얻었습니다. 나는 이것이 질문과 관련이 없기 때문에 당신이 이것으로부터 얻을 수있는 찬반을 깊이 파고 들지 않을 것입니다.

그러나 Iterable.forEach를 사용하는 반복은 FP에서 영감을 얻었으며 오히려 더 많은 FP를 Java로 가져 오는 결과입니다 (아이 론적으로는 순수한 FP에서 forEach를 많이 사용하지 않는다고 말합니다. 부작용).

결국 나는 그것이 현재 쓰고있는 취향 \ 스타일 \ 패러다임의 문제라고 말할 것이다.

병렬 처리

성능 관점에서 foreach (...)에 대해 Iterable.forEach를 사용하면 주목할만한 이점이 없습니다.

Iterable.forEach의 공식 문서에 따르면 :

모든 요소가 처리되거나 조치가 예외를 처리 할 때까지 반복 할 때 발생하는 순서대로 Iterable의 컨텐츠에 대해 지정된 조치를 수행합니다 .

... 즉, 문서는 암시 적 병렬 처리가 없다는 것을 분명히 알 수 있습니다. 하나를 추가하면 LSP 위반이됩니다.

이제, Java 8에는 "병렬 콜렉션"이 약속되어 있지만, 더 명확하게 작업하고 그것들을 사용하기 위해서는 약간의주의를 기울여야합니다 (예를 들어 mschenk74의 답변 참조).

BTW :이 경우 Stream.forEach 가 사용되며 실제 작업이 병렬로 수행된다고 보장하지는 않습니다 (기본 컬렉션에 따라 다름).

업데이트 : 눈에 띄지 않고 조금 뻗어 있지만 스타일과 가독성 관점에 또 다른 측면이 있습니다.

우선 평범한 오래된 forloops는 평범하고 오래되었습니다. 모두 이미 알고 있습니다.

둘째, 더 중요한 것은 아마도 하나의 라이너 람다에만 Iterable.forEach를 사용하고 싶을 것입니다. "몸"이 무거워지면 읽을 수없는 경향이 있습니다. 내부 클래스 (yuck) 또는 일반 오래된 forloop를 사용하는 두 가지 옵션이 있습니다. 사람들은 종종 동일한 코드베이스에서 동일한 것들 (컬렉션에 대한 항목)이 다양한 vay / 스타일로 수행되는 것을 보았을 때 짜증이납니다.

다시 말하지만 이것은 문제가 될 수도 있고 아닐 수도 있습니다. 코드 작업을하는 사람들에 따라 다릅니다.


1
병렬 처리에는 새로운 "병렬 콜렉션"이 필요하지 않습니다. 그것은 sequantial 스트림을 요청했는지 (collection.stream () 사용) 또는 병렬 스트림을 요청했는지 (collection.parallelStream () 사용)에 달려 있습니다.
JB 니 제트

@JBNizet docs Collection.parallelStream ()에 따르면 컬렉션 구현이 병렬 스트림을 반환한다고 보장하지는 않습니다. 나는 이것이 일어날 때 실제로 나 자신을 궁금해하고 있지만 아마도 이것은 수집에 달려 있습니다.
유진 로이

동의했다. 또한 컬렉션에 따라 다릅니다. 그러나 필자의 요점은 모든 표준 컬렉션 (ArrayList 등)에서 병렬 foreach 루프를 이미 사용할 수 있다는 것입니다. "병렬 컬렉션"을 기다릴 필요가 없습니다.
JB 니 제트

@JBNizet는 귀하의 요점에 동의하지만, 처음에 "병렬 컬렉션"이 의미하는 것은 아닙니다. 필자는 Java 8에서 스칼라 개념과 유사하게 "병렬 콜렉션"으로 추가 된 Collection.parallelStream ()을 참조한다. 또한 JSR의 비트에서 어떻게 호출되는지 잘 모르겠습니다.이 Java 8 기능에 동일한 용어를 사용하는 몇 가지 논문을 보았습니다.
Eugene Loy

1
마지막 단락에 대해 함수 참조를 사용할 수 있습니다.collection.forEach(MyClass::loopBody);
ratchet freak

6

가장 강화 된 기능상 forEach의 한계 중 하나는 점검 된 예외 지원이 없다는 것입니다.

가능한가지 해결 방법 은 터미널 forEach을 일반 이전 foreach 루프 로 바꾸는 것입니다 .

    Stream<String> stream = Stream.of("", "1", "2", "3").filter(s -> !s.isEmpty());
    Iterable<String> iterable = stream::iterator;
    for (String s : iterable) {
        fileWriter.append(s);
    }

다음은 람다 및 스트림 내에서 확인 된 예외 처리에 대한 다른 해결 방법과 함께 가장 많이 사용되는 질문 목록입니다.

예외를 발생시키는 Java 8 Lambda 함수?

Java 8 : Lambda-Streams, 예외가있는 메소드 별 필터링

Java 8 스트림 내부에서 CHECKED 예외를 발생시키는 방법은 무엇입니까?

Java 8 : 람다 식에서 필수 검사 예외 처리 왜 선택이 아닌 필수입니까?


3

1.7보다 향상된 Java 1.8 forEach 방법의 장점 for for loop는 코드를 작성하는 동안 비즈니스 로직에만 집중할 수 있다는 것입니다.

forEach 메소드는 java.util.function.Consumer 오브젝트를 인수로 사용하므로 비즈니스 로직을 별도의 위치에두고 언제든지 재사용 할 수 있습니다.

아래 스 니펫을 살펴보십시오.

  • 여기에 Consumer Class의 accept 클래스 메서드를 재정의하는 새 클래스를 만들었습니다. 여기에서 추가 기능을 추가 할 수 있습니다 .. !!!!!!

    class MyConsumer implements Consumer<Integer>{
    
        @Override
        public void accept(Integer o) {
            System.out.println("Here you can also add your business logic that will work with Iteration and you can reuse it."+o);
        }
    }
    
    public class ForEachConsumer {
    
        public static void main(String[] args) {
    
            // Creating simple ArrayList.
            ArrayList<Integer> aList = new ArrayList<>();
            for(int i=1;i<=10;i++) aList.add(i);
    
            //Calling forEach with customized Iterator.
            MyConsumer consumer = new MyConsumer();
            aList.forEach(consumer);
    
    
            // Using Lambda Expression for Consumer. (Functional Interface) 
            Consumer<Integer> lambda = (Integer o) ->{
                System.out.println("Using Lambda Expression to iterate and do something else(BI).. "+o);
            };
            aList.forEach(lambda);
    
            // Using Anonymous Inner Class.
            aList.forEach(new Consumer<Integer>(){
                @Override
                public void accept(Integer o) {
                    System.out.println("Calling with Anonymous Inner Class "+o);
                }
            });
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.