programing

Java 8 - 목록을 변환하는 가장 좋은 방법: 지도 또는 포어치?

prostudy 2022. 4. 22. 20:54
반응형

Java 8 - 목록을 변환하는 가장 좋은 방법: 지도 또는 포어치?

나는 리스트를 가지고 있다.myListToParse요소를 필터링하여 각 요소에 메서드를 적용하고 결과를 다른 목록에 추가하려는 경우myFinalList.

Java 8로 나는 그것을 두 가지 다른 방법으로 할 수 있다는 것을 알았다.나는 그들 사이의 더 효율적인 방법을 알고 싶고, 왜 한 방법이 다른 방법보다 나은지 이해하고 싶다.

나는 제3의 방법에 대한 어떠한 제안도 받아들일 수 있다.

방법 1:

myFinalList = new ArrayList<>();
myListToParse.stream()
        .filter(elt -> elt != null)
        .forEach(elt -> myFinalList.add(doSomething(elt)));

방법 2:.

myFinalList = myListToParse.stream()
        .filter(elt -> elt != null)
        .map(elt -> doSomething(elt))
        .collect(Collectors.toList()); 

성능 차이는 걱정하지 마십시오. 이 경우에는 보통 최소 수준일 겁니다.

방법 2는 다음과 같은 이유로 선호된다.

  1. 람다 표현식 외부에 존재하는 컬렉션을 돌연변이할 필요가 없다.

  2. 수집 파이프라인에서 수행되는 여러 단계가 순차적으로 작성되기 때문에 더 읽기 쉽다. 먼저 필터 작동, 그 다음 지도 작동, 그 결과 수집(수집 파이프라인의 이점에 대한 자세한 내용은 마틴 파울러의 우수 기사 참조)

  3. 당신은 값을 수집하는 방법을 쉽게 바꿀 수 있다.Collector사용된 것.어떤 경우에는 당신이 당신 자신의 것을 써야 할 수도 있다.Collector하지만 그 이점은 당신이 그것을 쉽게 재사용할 수 있다는 것이다.

두 번째 형태는 부작용이 없고 병렬화(평행 스트림만 사용)하기 쉽기 때문에 더 낫다는 기존 답변에 동의한다.

성능 측면에서, 병렬 스트림을 사용하기 시작할 때까지 동일한 것으로 표시됨.그렇다면, 지도는 훨씬 더 좋은 성과를 낼 수 있을 것이다.마이크로 벤치마크 결과 아래를 참조하십시오.

Benchmark                         Mode  Samples    Score   Error  Units
SO28319064.forEach                avgt      100  187.310 ± 1.768  ms/op
SO28319064.map                    avgt      100  189.180 ± 1.692  ms/op
SO28319064.mapWithParallelStream  avgt      100   55,577 ± 0,782  ms/op

같은 방법으로 첫 번째 예를 올릴 수는 없다. 왜냐하면 각 방법은 단자법이기 때문이다. 즉, 무효를 반환하기 때문에 여러분은 상태 좋은 람다를 사용할 수밖에 없기 때문이다.하지만 만약 당신이 평행 스트림을 사용하고 있다면 그것은 정말 나쁜 생각이다.

마지막으로 두 번째 코드 조각은 메서드 참조 및 정적 가져오기를 사용하여 약간 더 간결한 방법으로 작성할 수 있다는 점에 유의하십시오.

myFinalList = myListToParse.stream()
    .filter(Objects::nonNull)
    .map(this::doSomething)
    .collect(toList()); 

Eclipse 컬렉션을 사용할 경우collectIf()방법의

MutableList<Integer> source =
    Lists.mutable.with(1, null, 2, null, 3, null, 4, null, 5);

MutableList<String> result = source.collectIf(Objects::nonNull, String::valueOf);

Assert.assertEquals(Lists.immutable.with("1", "2", "3", "4", "5"), result);

그것은 열심히 평가하며 스트림을 사용하는 것보다 조금 더 빨라야 한다.

참고: 나는 Eclipse Collections의 커밋자입니다.

스트림을 사용할 때의 주요 이점 중 하나는 선언적 방법, 즉 프로그래밍의 기능적 스타일을 사용하여 데이터를 처리할 수 있는 능력을 제공한다는 것이다.그것은 또한 당신의 스트림을 동시에 만들기 위해 여분의 멀티스레드 코드를 쓸 필요가 없다는 것을 의미하는 무료 멀티스레딩 기능을 제공한다.

이러한 프로그래밍 스타일을 탐색하는 이유가 이러한 이점을 활용하기 때문이라고 가정하면 첫 번째 코드 샘플은 다음 이후 작동하지 않을 수 있다.foreach방법은 단자로 분류된다(측면을 생산할 수 있다는 의미).

맵 함수는 상태 비저장 람다 함수를 수용할 수 있기 때문에 기능 프로그래밍 관점에서 두 번째 방법이 선호된다.더 명시적으로, 지도 함수에 전달된 람다는 다음과 같아야 한다.

  1. 비간섭(non interferencing), 즉, 함수가 비전류일 경우(예:) 스트림의 소스를 변경해서는 안 된다는 의미.ArrayList).
  2. 상태 비저장 - 병렬 처리(스레드 스케줄링 차이로 인해 발생함) 시 예기치 않은 결과를 방지하십시오.

두 번째 접근방식의 또 다른 이점은 스트림이 평행하고 수집기가 동시에 정렬되지 않은 경우 이러한 특성은 수집을 동시에 수행하기 위한 감소 작업에 유용한 힌트를 제공할 수 있다는 것이다.

나는 두 번째 방법이 더 좋다.

첫 번째 방법을 사용할 때 성능을 향상시키기 위해 병렬 스트림을 사용하기로 결정하면 다음과 같은 방법으로 요소가 출력 목록에 추가되는 순서를 제어할 수 없게 된다.forEach.

사용할 때toList, 스트림 API는 병렬 스트림을 사용하더라도 순서를 보존한다.

세 번째 옵션이 있음 - 사용stream().toArray()- 스트림에 toList 메서드가 없는 이유의 주석을 참조하십시오.각() 또는 수집()보다 느리고 표현력이 떨어지는 것으로 나타났다.나중에 JDK 빌드에서 최적화될 수 있으므로, 만약을 위해 여기에 추가하십시오.

가정하여List<String>

    myFinalList = Arrays.asList(
            myListToParse.stream()
                    .filter(Objects::nonNull)
                    .map(this::doSomething)
                    .toArray(String[]::new)
    );

micro-micro 벤치마크, 1M 항목, 20% null 및 doSomething()의 단순 변환 포함

private LongSummaryStatistics benchmark(final String testName, final Runnable methodToTest, int samples) {
    long[] timing = new long[samples];
    for (int i = 0; i < samples; i++) {
        long start = System.currentTimeMillis();
        methodToTest.run();
        timing[i] = System.currentTimeMillis() - start;
    }
    final LongSummaryStatistics stats = Arrays.stream(timing).summaryStatistics();
    System.out.println(testName + ": " + stats);
    return stats;
}

결과는

병렬:

toArray: LongSummaryStatistics{count=10, sum=3721, min=321, average=372,100000, max=535}
forEach: LongSummaryStatistics{count=10, sum=3502, min=249, average=350,200000, max=389}
collect: LongSummaryStatistics{count=10, sum=3325, min=265, average=332,500000, max=368}

순차적:

toArray: LongSummaryStatistics{count=10, sum=5493, min=517, average=549,300000, max=569}
forEach: LongSummaryStatistics{count=10, sum=5316, min=427, average=531,600000, max=571}
collect: LongSummaryStatistics{count=10, sum=5380, min=444, average=538,000000, max=557}

null과 필터 없이 병렬로(따라서 스트림이SIZED):토레이는 이러한 경우에 최고의 성능을 발휘한다..forEach()receivient ArrayList에서 "indexOutOfBounds"로 대체해야 하는 오류 발생.forEachOrdered()

toArray: LongSummaryStatistics{count=100, sum=75566, min=707, average=755,660000, max=1107}
forEach: LongSummaryStatistics{count=100, sum=115802, min=992, average=1158,020000, max=1254}
collect: LongSummaryStatistics{count=100, sum=88415, min=732, average=884,150000, max=1014}

세 번째 Pary Libaries를 사용하는 것이 정상인 경우, 이 기능이 내장된 게으른 확장 컬렉션을 정의하십시오.예를 들어 우리는 간단하게 쓸 수 있다.

ListX myListToParse;

ListX myFinalList = myListToParse.filter(elt -> elt!=null) .map(elt -> doSomething(elt);

myFinalList는 첫 번째 액세스(그리고 구체화된 목록을 캐시하여 재사용한 후)까지 평가되지 않는다.

[공개 나는 사이클롭스 리액션의 선도 개발자다]

방법 3일 수도 있다.

나는 항상 논리를 분리하는 것을 선호한다.

Predicate<Long> greaterThan100 = new Predicate<Long>() {
    @Override
    public boolean test(Long currentParameter) {
        return currentParameter > 100;
    }
};
        
List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> resultList = sourceLongList.parallelStream().filter(greaterThan100).collect(Collectors.toList());

참조URL: https://stackoverflow.com/questions/28319064/java-8-best-way-to-transform-a-list-map-or-foreach

반응형