[java] 스트림은 언제 사용해야합니까?

a List와 그 stream()방법을 사용할 때 방금 질문을 받았습니다. 내가 알고 있지만 방법 을 사용하여, 나는 확신에 대한 아니에요 사용할 수 있습니다.

예를 들어, 다른 위치에 대한 다양한 경로가 포함 된 목록이 있습니다. 이제 주어진 단일 경로에 목록에 지정된 경로가 포함되어 있는지 확인하고 싶습니다. boolean조건이 충족되었는지 여부에 따라 를 반환하고 싶습니다 .

물론 이것은 어려운 작업이 아닙니다. 하지만 스트림을 사용해야하는지, 아니면 for (-each) 루프를 사용해야하는지 궁금합니다.

목록

private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
    "my/path/one",
    "my/path/two"
});

예-스트림

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream()
                        .map(String::toLowerCase)
                        .filter(path::contains)
                        .collect(Collectors.toList())
                        .size() > 0;
}

예-For-Each 루프

private boolean isExcluded(String path){
    for (String excludePath : EXCLUDE_PATHS) {
        if(path.contains(excludePath.toLowerCase())){
            return true;
        }
    }
    return false;
}

참고 것을 path매개 변수는 항상 소문자 .

내 첫 번째 추측은 for-each 접근 방식이 더 빠르다는 것입니다. 조건이 충족되면 루프가 즉시 반환되기 때문입니다. 필터링을 완료하기 위해 스트림은 모든 목록 항목을 계속 반복합니다.

내 가정이 맞습니까? 그렇다면 (또는 오히려 언제 ) 사용 stream()합니까?



답변

당신의 가정이 맞습니다. 스트림 구현이 for 루프보다 느립니다.

이 스트림 사용은 for 루프만큼 빠릅니다.

EXCLUDE_PATHS.stream()
                               .map(String::toLowerCase)
                               .anyMatch(path::contains);

이것은 항목을 반복 String::toLowerCase하고 항목에 하나씩 적용 하고 필터를 적용 하고 첫 번째 항목에서 종료합니다. 일치 .

collect()& 둘 다 anyMatch()터미널 작업입니다. anyMatch()그러나 collect()모든 항목을 처리해야하는 동안 처음 발견 된 항목에서 종료 됩니다.


답변

Streams 사용 여부는 성능 고려가 아니라 가독성에 따라 결정해야합니다. 실제로 성능과 관련하여 다른 고려 사항이 있습니다.

당신으로 .filter(path::contains).collect(Collectors.toList()).size() > 0접근, 당신은 모든 요소를 처리하고 임시로 수집List 하고 크기를 비교하기 전에 하지만 두 요소로 구성된 스트림에는 거의 문제가되지 않습니다.

.map(String::toLowerCase).anyMatch(path::contains)요소 수가 상당히 많은 경우을 사용 하면 CPU주기와 메모리를 절약 할 수 있습니다. 그래도 String일치 항목이 발견 될 때까지 각각 을 소문자 표현으로 변환합니다 . 분명히, 사용에 요점이 있습니다

private static final List<String> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .collect(Collectors.toList());

private boolean isExcluded(String path) {
    return EXCLUDE_PATHS.stream().anyMatch(path::contains);
}

대신. 따라서를 호출 할 때마다 소문자로의 변환을 반복 할 필요가 없습니다 isExcluded. EXCLUDE_PATHS문자열 의 요소 수 또는 길이가 정말 커지면 다음을 사용하는 것이 좋습니다.

private static final List<Predicate<String>> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .map(s -> Pattern.compile(s, Pattern.LITERAL).asPredicate())
          .collect(Collectors.toList());

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream().anyMatch(p -> p.test(path));
}

LITERAL플래그를 사용 하여 문자열을 정규식 패턴으로 컴파일하면 일반 문자열 작업처럼 동작하지만 엔진이 예를 들어 Boyer Moore 알고리즘을 사용하여 준비하는 데 약간의 시간을 소비하여 실제 비교와 관련하여 더 효율적입니다.

물론 이것은 준비에 소요되는 시간을 보상 할 수있는 충분한 후속 테스트가있는 경우에만 효과가 있습니다. 이 작업이 성능에 매우 중요한지 여부를 묻는 첫 번째 질문 외에 실제 성능 고려 사항 중 하나입니다. Streams를 사용할지 아니면for 루프 .

그건 그렇고, 위의 코드 예제는 원래 코드의 논리를 유지하므로 나에게는 의심스러워 보입니다. 귀하의 isExcluded방법을 반환 true, 그것은 반환하도록 지정된 경로가,리스트 내의 요소 중 하나를 포함하는 경우 true에 대한 /some/prefix/to/my/path/one뿐만 아니라,로 my/path/one/and/some/suffix또는 /some/prefix/to/my/path/one/and/some/suffix.

심지어 dummy/path/onerouscontains문자열 로 기준을 충족하는 것으로 간주됩니다 my/path/one.


답변

네. 당신이 옳습니다. 스트림 접근 방식에는 약간의 오버 헤드가 있습니다. 그러나 다음과 같은 구성을 사용할 수 있습니다.

private boolean isExcluded(String path) {
    return  EXCLUDE_PATHS.stream().map(String::toLowerCase).anyMatch(path::contains);
}

스트림을 사용하는 주된 이유는 코드를 더 간단하고 읽기 쉽게 만들기 때문입니다.


답변

Java 스트림의 목표는 병렬 코드 작성의 복잡성을 단순화하는 것입니다. 함수형 프로그래밍에서 영감을 얻었습니다. 직렬 스트림은 코드를 더 깔끔하게 만드는 것입니다.

성능을 원한다면, 설계된 parallelStream을 사용해야합니다. 일반적으로 시리얼은 더 느립니다.

좋은에 대해 읽을 수있는 글이 , 실적 ForLoopStreamParallelStream .

코드에서 종료 방법을 사용하여 첫 번째 일치에서 검색을 중지 할 수 있습니다. (anyMatch …)


답변

다른 사람들이 많은 좋은 점을 언급했듯이 스트림 평가에서 지연 평가 를 언급하고 싶습니다 . map()소문자 경로의 스트림을 생성 할 때 전체 스트림을 즉시 생성하지 않고 대신 스트림이 느리게 구성 되므로 성능이 기존 for 루프와 동일해야합니다. 그것은 전체 검사를 수행하지 않는, map()그리고 anyMatch()같은 시간에 실행됩니다. anyMatch()true를 반환 하면 단락됩니다.


답변