[java] 카운트에서 평가되지 않은 중간 스트림 작업

Java가 스트림 작업을 스트림 파이프 라인으로 구성하는 방법을 이해하는 데 어려움을 겪고있는 것 같습니다.

다음 코드를 실행할 때

public
 static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

콘솔 만 인쇄합니다 4. StringBuilder객체는 여전히 값을 가진다 "".

필터 작업을 추가하면 : filter(s -> true)

public static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .filter(s -> true)
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

출력은 다음과 같이 변경됩니다.

4
1234

중복 적으로 보이는이 필터 작업은 구성된 스트림 파이프 라인의 동작을 어떻게 변경합니까?



답변

count()터미널 작업은 JDK의 내 버전에서 다음 코드를 실행 끝 :

if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
    return spliterator.getExactSizeIfKnown();
return super.evaluateSequential(helper, spliterator);

가있는 경우 filter()작업의 파이프 라인에서 작업이 (이후, 처음 알려진 스트림의 크기는 더 이상 알 수없는 filter스트림의 일부 요소를 거부 할 수있다). 따라서 if블록이 실행되지 않고 중간 작업이 실행되어 StringBuilder가 수정됩니다.

반면 map()파이프 라인 에만있는 경우 스트림의 요소 수는 초기 요소 수와 동일해야합니다. 따라서 if 블록이 실행되고 중간 작업을 평가하지 않고 크기가 직접 반환됩니다.

전달 된 람다 map()는 문서에 정의 된 계약 을 위반합니다. 이는 방해가되지 않는 상태 비 저장 작업이어야하지만 상태 비 저장이 아닙니다. 따라서 두 경우 모두 다른 결과를 갖는 것은 버그로 간주 될 수 없습니다.


답변

에서 JDK-9 그것은 분명히 자바 문서에 설명 된

부작용을 피하는 것도 놀랍습니다. Each 및 forEachOrdered에 대한 터미널 작업을 제외하고, 스트림 구현이 계산 결과에 영향을주지 않고 행동 매개 변수의 실행을 최적화 할 수있는 경우 행동 매개 변수의 부작용이 항상 실행되는 것은 아닙니다. 구체적인 예는 count 작업 에 대한 API 참고를 참조하십시오 .

API 참고 사항 :

구현은 스트림 소스로부터 직접 카운트를 계산할 수있는 경우 스트림 파이프 라인을 순차적으로 또는 병렬로 실행하지 않도록 선택할 수 있습니다. 이러한 경우 소스 요소가 순회되지 않으며 중간 작업이 평가되지 않습니다. 디버깅과 같은 무해한 경우를 제외하고는 권장하지 않는 부작용이있는 동작 매개 변수가 영향을받을 수 있습니다. 예를 들어 다음 스트림을 고려하십시오.

 List<String> l = Arrays.asList("A", "B", "C", "D");
 long count = l.stream().peek(System.out::println).count();

스트림 소스가 다루는 요소의 수인 List가 알려져 있으며 중간 조작 peek은 스트림에 요소를 삽입하거나 제거하지 않습니다 (flatMap 또는 필터 조작의 경우와 같이). 따라서 카운트는 List의 크기이며 파이프 라인을 실행할 필요가 없으며 부작용으로 목록 요소를 인쇄합니다.


답변

이것은 .map의 목적이 아닙니다. “Something”스트림을 “Something Else”스트림으로 변환하는 데 사용됩니다. 이 경우, map을 사용하여 외부 Stringbuilder에 문자열을 추가 한 후, “Stringbuilder”스트림을 갖게됩니다. 각 스트림은 맵 조작으로 작성되어 하나의 숫자를 원래 Stringbuilder에 추가합니다.

스트림은 실제로 스트림에서 매핑 된 결과를 사용하여 작업을 수행하지 않으므로 스트림 프로세서가 단계를 건너 뛸 수 있다고 가정하는 것이 합리적입니다. 작업을 수행하기 위해 부작용에 의존하고 있습니다.이 기능은 맵의 기능 모델을 손상시킵니다. forEach를 사용하면 더 나은 서비스를받을 수 있습니다. 계산을 별도의 스트림으로 전체적으로 수행하거나 forEach에 AtomicInt를 사용하여 카운터를 배치하십시오.

필터는 이제 각 스트림 요소에 대해 의미있는 의미가있는 작업을 수행해야하므로 스트림 내용을 강제로 실행합니다.


답변