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


33

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

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


2
재미있는 !!!
uneq95

3
나는 이것이 구현에 특정한 행동이라고 상상할 것이다. 아마도 첫 번째 스트림은 알려진 크기를 갖지만 두 번째 스트림은 그렇지 않기 때문에 크기가 중간 작업의 실행 여부를 결정합니다.
Andy Turner

관심이 없다면 필터와 맵을 반전 시키면 어떻게됩니까?
Andy Turner

Haskell에서 비트를 프로그래밍하면 약간의 게으른 평가와 같은 냄새가납니다. 구글 검색 결과, 스트림에 게으름이 생겼습니다. 그럴 수 있습니까? 그리고 필터가 없으면 자바가 충분히 영리하다면 실제로 매핑을 실행할 필요가 없습니다.
프레데릭

@AndyTurner 반전에도 동일한 결과를 제공합니다
uneq95

답변:


39

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

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

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

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

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


flatMap()요소의 수를 변경할 수 있기 때문에 처음에는 열망했던 이유 (현재 게으른)입니까? 따라서 대안은 현재 형태로 계약을 위반하는 forEach()경우 별도로 사용 하고 계산 하는 것 map()입니다.
프레데릭

3
flatMap에 관해서는 그렇게 생각하지 않습니다. 그것은 AFAIK였습니다. 왜냐하면 그것을 간절히하는 것이 초기에 더 간단했기 때문입니다. 예, map ()과 함께 스트림을 사용하여 부작용을 만드는 것은 좋지 않습니다.
JB 니 제트

4 1234여분의 필터를 사용하거나 map () 작업에서 부작용을 일으키지 않고 전체 출력을 얻는 방법에 대한 제안이 있습니까?
atalantus

1
int count = array.length; String result = String.join("", array);
JB 니 제트

1
또는 실제로 StringBuilder를 사용하려는 경우 forEach를 사용하거나 다음을 사용할 수 있습니다.Collectors.joining("")
njzk2

19

에서 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의 크기이며 파이프 라인을 실행할 필요가 없으며 부작용으로 목록 요소를 인쇄합니다.


0

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

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.