2016-09-14 3 views
1

Java 8 Stream의 내용을 List에 여러 번 추가하는 코드를 작성해야하며,이를 수행하는 가장 좋은 방법을 알아내는 데 어려움이 있습니다. 다른 곳에서, 나는 다음과 같은 옵션을 좁혀했습니다 : 나는 SO (How to add elements of a Java8 stream into an existing List 주로이 질문에) :에 읽은 것을 바탕으로스트림의 요소를 기존 목록에 추가하는 더 좋은 방법은 무엇입니까?

import java.util.ArrayList; 
import java.util.List; 
import java.util.function.Function; 
import java.util.stream.Collectors; 

public class Accumulator<S, T> { 


    private final Function<S, T> transformation; 
    private final List<T> internalList = new ArrayList<T>(); 

    public Accumulator(Function<S, T> transformation) { 
     this.transformation = transformation; 
    } 

    public void option1(List<S> newBatch) { 
     internalList.addAll(newBatch.stream().map(transformation).collect(Collectors.toList())); 
    } 

    public void option2(List<S> newBatch) { 
     newBatch.stream().map(transformation).forEach(internalList::add); 
    } 
} 
아이디어는 방법은 동일한 인스턴스에 대해 여러 번 호출 할 것입니다

Accumulator. 중간 목록을 사용하고 스트림 외부에서 Collection.addAll()을 한 번 호출하거나 각 요소의 스트림에서 collection.add()을 호출하는 것 중에서 선택합니다.

기능적 프로그래밍의 정신에서 더 많은 옵션 2를 선호하는 경향이 있으며 중간 목록을 만들지는 않지만 n이 클 때 add() 번을 호출하는 대신 addAll()을 호출하면 이점이있을 수 있습니다.

두 옵션 중 하나가 다른 옵션보다 훨씬 좋습니까?

EDIT : JB Nizet은 모든 배치가 추가 될 때까지 변환을 지연시키는 매우 차가운 answer을 가지고 있습니다. 필자의 경우 변환이 곧바로 수행되어야합니다.

PS는 : 내 예제 코드에서, 나는 스트림

+0

분해 된 바이트 코드 (javap)는 –

+1

조숙 한 최적화를 수행하지 마십시오. 무엇보다 깨끗하고, 성능 문제가 발생한 경우에만이 코드를 프로파일 러로 확인하십시오. –

+0

'addAll()'을 호출 할 때 어떤 이점도 있다고 생각하지 않습니다. – shmosel

답변

4

먼저

당신의 두 번째 변형은 다음과 같아야합니다.

public void option2(List<S> newBatch) { 
    newBatch.stream().map(transformation).forEachOrdered(internalList::add); 
} 

이어야합니다. 게다가

,

public void option1(List<S> newBatch) { 
    internalList.addAll(newBatch.stream().map(transformation).collect(Collectors.toList())); 
} 

에서 addAll의 장점은 Collector API는 스트림이 콜렉터의 예상 크기에 대한 힌트를 제공하는 것을 허용하지 않기 때문에 논쟁하고의 누적 기능을 평가하기 위해 스트림을 필요로 모든 요소는 현재 구현 된 ArrayList::add 이외의 요소입니다. 이 방법은 addAll에서 어떤 혜택을받을 수 있기 전에

그래서, 그것은 반복적으로 잠재 용량 증가 작업을 포함하여 ArrayListadd를 호출하여 ArrayList을 가득 채웠다. 따라서 option2으로 후회하지 않아도됩니다.

대안 임시 모음에 대한 스트림 빌더를 사용하는 것입니다

public class Accumulator<S, T> { 
    private final Function<S, T> transformation; 
    private final Stream.Builder<T> internal = Stream.builder(); 

    public Accumulator(Function<S, T> transformation) { 
     this.transformation = transformation; 
    } 

    public void addBatch(List<S> newBatch) { 
     newBatch.stream().map(transformation).forEachOrdered(internal); 
    } 

    public List<T> finish() { 
     return internal.build().collect(Collectors.toList()); 
    } 
} 

스트림 빌더의 용량을 증가 할 때 내용을 복사 필요로하지 않는 긴가 버퍼를 사용하지만 해결책은 여전히 ​​사실을 앓고 최종 수집 단계는 적절한 초기 용량없이 (현재 구현에서) ArrayList을 채우는 것을 포함합니다. 현재 구현으로

, 그것은

public List<T> finish() { 
    return Arrays.asList(internal.build().toArray(…)); 
} 

등의 마무리 단계를 구현하기 위해 훨씬 더 효율적 그러나 이것은 우리가 일반적인 배열 유형에 대해 그렇게 할 수 없기 때문에 (어느 호출자가 제공하는 IntFunction<T[]>이 필요) 또는 검사되지 않은 작업 (Object[]T[] 인 것처럼 여기는 것이 좋지만, 여전히 좋지 않은 검사되지 않은 작업)을 수행 할 수 있습니다.

5

에서 수행 될 필요가 어떤 작업을위한 자리로 transformation을 사용했습니다 가장 좋은 방법은 피하고, 세 번째 하나가 될 것이라고 내부 목록을 완전히 . 그냥 스트림 당신을위한 최종 목록을 만들 수 있습니다 : 같은 변환이 적용되어야하는 당신의 N 배치를 포함, 당신이 할 것입니다 당신이 List<List<S>>이 가정 모두의

List<T> result = 
    batches.stream() 
      .flatMap(batch -> batch.stream()) 
      .map(transformation) 
      .collect(Collectors.toList()); 
+0

이것은 고정 크기 목록을 만들 때 가장 좋은 옵션입니다. –

+2

좋은 답변이지만 모든 배치가 준비 될 때까지 이전 배치의 처리를 연기 할 수 있어야합니다. 항상 그런 것은 아닙니다. – Andreas

+0

좋은 대답은, 나는 생각하지 않았을 것이지만, 제 경우에는 처리를 연기하지 않으려 고합니다. @ Andreas가 언급했습니다. 나는 적절하게 질문을 편집하고있다. – ahelix

관련 문제