본문 바로가기

최종 연산과 기본형 Stream

찾기·일치 검사·축약·수집 최종 연산을 구분하고 빈 Optional 결과와 박싱 비용을 안전하게 처리합니다.

최종 연산은 파이프라인을 소비해 값, Optional, 배열, 컬렉션 또는 부수 효과를 만듭니다.

같은 중간 연쇄도 최종 연산에 따라 필요한 원소 수와 반환 타입이 달라집니다.

결과 원소 수와 빈 원본 정책을 먼저 정해야 get() 호출이나 잘못된 항등값 사용을 피할 수 있습니다.


빈 findFirst 결과의 get 호출

조건을 만족하는 값이 없으면 findFirst()는 빈 Optional을 반환합니다.

아래 고정 입력은 빈 Optional을 만듭니다. 여기에 get()을 호출하면 API 계약상 NoSuchElementException을 던집니다.

lab/EmptyFindFirstFailure.java
import java.util.List;

public final class EmptyFindFirstFailure {
    public static void main(String[] args) {
        String value =
                List.of("stream", "collector").stream()
                        .filter(title -> title.startsWith("z"))
                        .findFirst()
                        .get();
        System.out.println(value);
    }
}

값이 반드시 있어야 하는 불변식이라면 의미 있는 메시지를 담은 orElseThrow를 사용하고, 부재가 정상이라면 Optional 반환이나 대체 경로를 씁니다.

null로 되돌리면 호출자가 다시 누락을 추적해야 합니다.


최종 연산의 결과 형태

count는 long 하나, min·max·find는 Optional, anyMatch·allMatch·noneMatch는 boolean, reduce는 축약 값, toList·collect는 컨테이너, toArray는 배열을 반환합니다.

forEach는 반환값 없이 부수 효과를 실행합니다.

src/TerminalOperationCatalog.java
import java.util.Comparator;
import java.util.List;

public final class TerminalOperationCatalog {
    public static void main(String[] args) {
        List<Integer> values = List.of(3, 7, 10, 12);
        System.out.println("count=" + values.stream().filter(v -> v >= 7).count());
        System.out.println("max=" + values.stream().max(Comparator.naturalOrder()).orElseThrow());
        System.out.println("any-even=" + values.stream().anyMatch(v -> v % 2 == 0));
        System.out.println("all-positive=" + values.stream().allMatch(v -> v > 0));
        System.out.println("sum=" + values.stream().reduce(0, Integer::sum));
        System.out.println("array-length=" + values.stream().toArray(Integer[]::new).length);
    }
}

각 최종 연산은 새 Stream에서 호출합니다.

빈 원본에서 allMatch와 noneMatch는 검사할 반례가 없어 true를 반환합니다.

“데이터가 하나 이상 존재했다”는 조건까지 필요하다면 count나 별도 입력 검사를 결합합니다.

독립 확인: 빈 List<Integer>에서 양수 조건의 anyMatch·allMatch·noneMatch와 findFirst를 계산합니다. 세 boolean 중 true가 있으면 실제 양수 값이 존재했다는 뜻인가요? 새 Stream에서 각각 호출합니다.

풀이 확인

anyMatch=false, allMatch=true, noneMatch=true이며 findFirst는 빈 Optional입니다. 양수 여부를 검사할 원소가 없으므로 모든 원소가 양수라는 명제와 양수가 하나도 없다는 명제에 반례가 없습니다. 존재 여부는 anyMatch나 별도 count>0 조건으로 판단해야 하며 true라는 반환값 하나만으로 실제 값을 얻었다고 말하지 않습니다.


단락 평가 최종 연산

anyMatch는 true, allMatch는 false, noneMatch는 true인 원소 하나로 결과를 확정할 수 있어 모든 원소를 검사하지 않아도 됩니다.

findFirst와 findAny도 하나를 얻으면 종료할 수 있습니다.

따라서 부수 효과 호출 횟수가 원본 크기와 같다고 가정해서는 안 됩니다.

src/ShortCircuitTerminalCounts.java
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public final class ShortCircuitTerminalCounts {
    public static void main(String[] args) {
        AtomicInteger checks = new AtomicInteger();
        boolean found =
                List.of(1, 3, 4, 6, 8).stream()
                        .anyMatch(
                                value -> {
                                    checks.incrementAndGet();
                                    return value % 2 == 0;
                                });
        System.out.println("found=" + found + ", checks=" + checks.get());
    }
}

원문의 순차 순회를 따라가면 4에서 처음 true가 되어 checks=3으로 계산됩니다. 이는 특정 부수 효과 호출 횟수를 보장하는 API 계약이나 새 실행 기록은 아닙니다.

제품 코드는 checks 자체를 업무 결과로 삼지 않습니다.

noneMatch는 조건식이 참인 값을 하나 만나면 “하나도 없다”는 주장을 반박해 결과 false를 확정합니다. 조건식 반환값과 최종 연산의 반환값을 구별하세요. matching 최종 연산의 계약은 빈 원본과 단락 평가를 명시하지만, 일반적인 사용자 콜백 횟수를 업무 계약으로 고정하지 않습니다.

병렬 anyMatch에서는 호출 수와 순서가 더 달라질 수 있습니다.


reduce의 항등값과 결합 법칙

reduce는 여러 원소를 하나로 결합합니다.

항등값은 빈 원본의 결과이면서 결합 함수의 중립값이어야 합니다.

덧셈의 0, 곱셈의 1, 문자열 연결의 빈 문자열이 예입니다.

min에 임의의 0을 항등값으로 넣으면 모두 양수인 원본에서도 잘못된 0이 남을 수 있으므로 항등값이 없는 reduce가 낫습니다.

reduce 계약은 누산 함수의 결합 법칙과 간섭이 없고 상태에 의존하지 않는 동작을 요구합니다. 세 인수 형태에서는 결합 함수도 항등값과 누산 함수에 맞는 결합 규칙을 만족해야 합니다.

뺄셈처럼 묶는 순서에 따라 값이 달라지는 연산은 병렬 reduce에 맞지 않습니다.

src/SafeReductionExamples.java
import java.util.List;
import java.util.OptionalInt;

public final class SafeReductionExamples {
    public static void main(String[] args) {
        int sum = List.of(5, 7, 9).stream().reduce(0, Integer::sum);
        OptionalInt minimum = List.of(5, 7, 9).stream().mapToInt(Integer::intValue).min();
        String joined =
                List.of("stream", "terminal").stream()
                        .reduce("", (left, right) -> left.isEmpty() ? right : left + "," + right);
        System.out.println("sum=" + sum + ", min=" + minimum.orElseThrow() + ", joined=" + joined);
    }
}

중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다

a·빈 문자열·b의 두 괄호 묶음에서 중간값이 a, 또는 b가 되어 a,,b와 a,b로 갈립니다. 두 독립 계산의 중간값을 추적하면 결합법칙 위반이 결과 차이를 만드는 이유가 드러납니다.

중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다

SafeReductionExamples의 문자열 누산 함수 f를 a, 빈 문자열, b에 적용합니다.

결합 법칙을 깨는 반례

중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다

f(left, right)는 left가 비면 right를, 아니면 left + ',' + right를 반환합니다. a·빈 문자열·b를 묶는 순서에 따라 결과가 달라집니다.

같은 세 입력의 괄호 위치가 다른 문자열을 만든다원함수f는왼쪽이빈문자열이면오른쪽만,아니면쉼표로연결한다. 같은a,빈문자열,b를왼쪽먼저묶으면f(a,empty)=a콤마뒤에b를연결하여a,,b이고오른쪽먼저f(empty,b)=b를만들면f(a,b)=a,b이다. 각경로는괄호계산의의존관계이지시간순서나병렬실행실측이아니다. 5노드4간선/mobile동일하며원반례aux계산식모두보존한다.같은 세 입력a · 빈 문자열 · bf("a", "")중간값 "a,"f("a,", "b")결과 "a,,b"f("", "b")중간값 "b"f("a", "b")결과 "a,b"
같은 세 입력의 괄호 위치가 다른 문자열을 만든다원함수f는왼쪽이빈문자열이면오른쪽만,아니면쉼표로연결한다. 같은a,빈문자열,b를왼쪽먼저묶으면f(a,empty)=a콤마뒤에b를연결하여a,,b이고오른쪽먼저f(empty,b)=b를만들면f(a,b)=a,b이다. 각경로는괄호계산의의존관계이지시간순서나병렬실행실측이아니다. 5노드4간선/mobile동일하며원반례aux계산식모두보존한다.같은 세 입력a · 빈 문자열 · bf("a", "")중간값 "a,"f("a,", "b")결과 "a,,b"f("", "b")중간값 "b"f("a", "b")결과 "a,b"

왼쪽부터

중간 계산
f("a", "") = "a," 뒤 f("a,", "b")
결과
"a,,b"

오른쪽부터

중간 계산
f("", "b") = "b" 뒤 f("a", "b")
결과
"a,b"

f는 원문 문자열 누산 함수입니다. main은 비어 있지 않은 stream·terminal만 사용하며, 위 반례는 실행 관측이 아닌 함수식 계산입니다.

이 예의 조건

  • 괄호 계산의 의존 관계이며 병렬 실측이 아닙니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2원 함수 f는 왼쪽이 빈 문자열이면 오른쪽만 반환하고, 아니면 쉼표로 연결한다. 같은 a, 빈 문자열, b를 왼쪽부터 묶으면 f(a,empty)=a 콤마 뒤에 b를 연결하여 a,,b이고, 오른쪽부터 f(empty,b)=b를 만들면 f(a,b)=a,b이다. 각 경로는 괄호 계산의 의존 관계이며 시간순서나 병렬 실행을 실측한 결과가 아니다. 5개 노드와 4개 간선은 mobile에서도 동일하며 원 반례의 aux 계산식을 모두 보존한다.
원 도표의 설명과 주석
구분내용
설명원 함수 f는 왼쪽이 빈 문자열이면 오른쪽만 반환하고, 아니면 쉼표로 연결한다. 같은 a, 빈 문자열, b를 왼쪽부터 묶으면 f(a,empty)=a 콤마 뒤에 b를 연결하여 a,,b이고, 오른쪽부터 f(empty,b)=b를 만들면 f(a,b)=a,b이다. 각 경로는 괄호 계산의 의존 관계이며 시간순서나 병렬 실행을 실측한 결과가 아니다. 5개 노드와 4개 간선은 mobile에서도 동일하며 원 반례의 aux 계산식을 모두 보존한다.
전체 읽기 정보중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다 중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다 SafeReductionExamples의 문자열 누산 함수 f를 a, 빈 문자열, b에 적용합니다. 결합 법칙을 깨는 반례 중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다 f(left, right)는 left가 비면 right를, 아니면 left + ',' + right를 반환합니다. a·빈 문자열·b를 묶는 순서에 따라 결과가 달라집니다. 왼쪽부터 중간 계산 f("a", "") = "a," 뒤 f("a,", "b") 결과 "a,,b" 오른쪽부터 중간 계산 f("", "b") = "b" 뒤 f("a", "b") 결과 "a,b" f는 원문 문자열 누산 함수입니다. main은 비어 있지 않은 stream·terminal만 사용하며, 위 반례는 실행 관측이 아닌 함수식 계산입니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
같은 입력

a, 빈 문자열, b를 두 괄호 방식에 둡니다.

  • f 조건: 왼쪽이 빈 값이면 오른쪽
  • 그 외: 쉼표 연결
  • 같은 함수도 묶는 방식에 따라 중간값이 다릅니다.
각 첫 묶음

왼쪽은 f(a,empty), 오른쪽은 f(empty,b)를 계산합니다.

  • 왼쪽 중간값: a,
  • 오른쪽 중간값: b
  • 빈 값 조건의 위치가 쉼표 생성을 바꿉니다.
최종 반례

각 중간값에 남은 입력을 결합합니다.

  • 왼쪽: a,,b
  • 오른쪽: a,b
  • 결과가 달라 결합법칙을 만족하지 않습니다.

중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다

a·빈 문자열·b의 두 괄호 묶음에서 중간값이 a, 또는 b가 되어 a,,b와 a,b로 갈립니다. 두 독립 계산의 중간값을 추적하면 결합법칙 위반이 결과 차이를 만드는 이유가 드러납니다.

중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다

SafeReductionExamples의 문자열 누산 함수 f를 a, 빈 문자열, b에 적용합니다.

결합 법칙을 깨는 반례

중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다

f(left, right)는 left가 비면 right를, 아니면 left + ',' + right를 반환합니다. a·빈 문자열·b를 묶는 순서에 따라 결과가 달라집니다.

같은 세 입력의 괄호 위치가 다른 문자열을 만든다원함수f는왼쪽이빈문자열이면오른쪽만,아니면쉼표로연결한다. 같은a,빈문자열,b를왼쪽먼저묶으면f(a,empty)=a콤마뒤에b를연결하여a,,b이고오른쪽먼저f(empty,b)=b를만들면f(a,b)=a,b이다. 각경로는괄호계산의의존관계이지시간순서나병렬실행실측이아니다. 5노드4간선/mobile동일하며원반례aux계산식모두보존한다.같은 세 입력a · 빈 문자열 · bf("a", "")중간값 "a,"f("a,", "b")결과 "a,,b"f("", "b")중간값 "b"f("a", "b")결과 "a,b"
같은 세 입력의 괄호 위치가 다른 문자열을 만든다원함수f는왼쪽이빈문자열이면오른쪽만,아니면쉼표로연결한다. 같은a,빈문자열,b를왼쪽먼저묶으면f(a,empty)=a콤마뒤에b를연결하여a,,b이고오른쪽먼저f(empty,b)=b를만들면f(a,b)=a,b이다. 각경로는괄호계산의의존관계이지시간순서나병렬실행실측이아니다. 5노드4간선/mobile동일하며원반례aux계산식모두보존한다.같은 세 입력a · 빈 문자열 · bf("a", "")중간값 "a,"f("a,", "b")결과 "a,,b"f("", "b")중간값 "b"f("a", "b")결과 "a,b"

왼쪽부터

중간 계산
f("a", "") = "a," 뒤 f("a,", "b")
결과
"a,,b"

오른쪽부터

중간 계산
f("", "b") = "b" 뒤 f("a", "b")
결과
"a,b"

f는 원문 문자열 누산 함수입니다. main은 비어 있지 않은 stream·terminal만 사용하며, 위 반례는 실행 관측이 아닌 함수식 계산입니다.

이 예의 조건

  • 괄호 계산의 의존 관계이며 병렬 실측이 아닙니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2원 함수 f는 왼쪽이 빈 문자열이면 오른쪽만 반환하고, 아니면 쉼표로 연결한다. 같은 a, 빈 문자열, b를 왼쪽부터 묶으면 f(a,empty)=a 콤마 뒤에 b를 연결하여 a,,b이고, 오른쪽부터 f(empty,b)=b를 만들면 f(a,b)=a,b이다. 각 경로는 괄호 계산의 의존 관계이며 시간순서나 병렬 실행을 실측한 결과가 아니다. 5개 노드와 4개 간선은 mobile에서도 동일하며 원 반례의 aux 계산식을 모두 보존한다.
원 도표의 설명과 주석
구분내용
설명원 함수 f는 왼쪽이 빈 문자열이면 오른쪽만 반환하고, 아니면 쉼표로 연결한다. 같은 a, 빈 문자열, b를 왼쪽부터 묶으면 f(a,empty)=a 콤마 뒤에 b를 연결하여 a,,b이고, 오른쪽부터 f(empty,b)=b를 만들면 f(a,b)=a,b이다. 각 경로는 괄호 계산의 의존 관계이며 시간순서나 병렬 실행을 실측한 결과가 아니다. 5개 노드와 4개 간선은 mobile에서도 동일하며 원 반례의 aux 계산식을 모두 보존한다.
전체 읽기 정보중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다 중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다 SafeReductionExamples의 문자열 누산 함수 f를 a, 빈 문자열, b에 적용합니다. 결합 법칙을 깨는 반례 중간에 빈 문자열이 있으면 묶는 순서가 결과를 바꾼다 f(left, right)는 left가 비면 right를, 아니면 left + ',' + right를 반환합니다. a·빈 문자열·b를 묶는 순서에 따라 결과가 달라집니다. 왼쪽부터 중간 계산 f("a", "") = "a," 뒤 f("a,", "b") 결과 "a,,b" 오른쪽부터 중간 계산 f("", "b") = "b" 뒤 f("a", "b") 결과 "a,b" f는 원문 문자열 누산 함수입니다. main은 비어 있지 않은 stream·terminal만 사용하며, 위 반례는 실행 관측이 아닌 함수식 계산입니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
같은 입력

a, 빈 문자열, b를 두 괄호 방식에 둡니다.

  • f 조건: 왼쪽이 빈 값이면 오른쪽
  • 그 외: 쉼표 연결
  • 같은 함수도 묶는 방식에 따라 중간값이 다릅니다.
각 첫 묶음

왼쪽은 f(a,empty), 오른쪽은 f(empty,b)를 계산합니다.

  • 왼쪽 중간값: a,
  • 오른쪽 중간값: b
  • 빈 값 조건의 위치가 쉼표 생성을 바꿉니다.
최종 반례

각 중간값에 남은 입력을 결합합니다.

  • 왼쪽: a,,b
  • 오른쪽: a,b
  • 결과가 달라 결합법칙을 만족하지 않습니다.

이 문자열 누산 함수는 중간에 빈 문자열이 올 수 있는 일반 입력에서 결합 법칙을 만족하지 않습니다. 문자열 연결은 Collectors.joining(",")이 의도를 직접 표현합니다.

예시 예제의 main의 sum은 5+7+9=21이고 min은 5, 두 문자열의 순차 누산 결과는 stream,terminal입니다. 그러나 f("a", "")는 a,이고 f("a,", "b")는 a,,b입니다. 먼저 f("", "b")를 계산하면 b여서 f("a", "b")는 a,b입니다. 같은 세 원소라도 괄호 위치가 결과를 바꾸므로 이 누산 함수를 일반적인 reduce에 사용하지 않습니다. 단순히 parallel을 호출하지 않았다는 조건으로 결합 법칙 요구가 없어지는 것도 아닙니다.

독립 확인: [5,7,9]에서 reduce(0, Integer::min)과 mapToInt(...).min()의 값을 비교합니다. 별도로 [a,빈 문자열,b]를 Collectors.joining(",")으로 수집하면 어떤 문자열인가요? 첫 문제에서 0은 무엇을 잘못 가정했나요?

풀이 확인

0과 5입니다. 0은 min(0,t)=t인 중립값이 아니어서 실제 원소보다 작은 0이 결과로 남습니다. joining은 원소 사이에 쉼표를 넣어 a,,b이며 빈 가운데 원소도 그 위치를 유지합니다. 잘못된 f의 한쪽 묶음이 같은 문자열을 만들었다는 것만으로 결합 법칙 반례가 없어지지 않습니다.


기본형 스트림과 박싱

Stream<Integer>는 각 숫자를 래퍼 객체로 다루고 일반 reduce를 사용합니다.

IntStream은 기본형 int를 직접 다루며 sum, average, summaryStatistics를 제공합니다.

객체 스트림에서 mapToInt로 전환하고 객체 결과가 필요할 때는 boxed로 돌아옵니다.

app/PrimitiveBoardStatistics.java
import java.util.IntSummaryStatistics;
import java.util.List;

public final class PrimitiveBoardStatistics {
    private record PostMetric(String title, int viewCount) {}

    public static void main(String[] args) {
        List<PostMetric> posts =
                List.of(
                        new PostMetric("welcome", 45),
                        new PostMetric("maintenance", 60),
                        new PostMetric("question", 30));
        IntSummaryStatistics stats =
                posts.stream().mapToInt(PostMetric::viewCount).summaryStatistics();
        double average = posts.stream().mapToInt(PostMetric::viewCount).average().orElse(0.0);
        List<Integer> popularViewCounts =
                posts.stream()
                        .mapToInt(PostMetric::viewCount)
                        .filter(value -> value >= 40)
                        .boxed()
                        .toList();
        System.out.println(
                "count="
                        + stats.getCount()
                        + ", sum="
                        + stats.getSum()
                        + ", min="
                        + stats.getMin()
                        + ", max="
                        + stats.getMax()
                        + ", avg="
                        + average);
        System.out.println(popularViewCounts);
    }
}

빈 IntStream의 평균과 min은 Optional 계열이고 summaryStatistics의 최솟값·최댓값은 경계값을 가질 수 있으므로 count를 함께 확인합니다.

기본형 스트림이 항상 더 빠르다고 단정하지 않고 할당과 작업 부하를 측정합니다.

예제의 통계는 세 조회값 45·60·30을 모두 누산해 count 3·sum 135·min 30·max 60이며 average는 45.0입니다. 별도 인기 조회 목록은 >=40 조건 뒤 boxed로 [45,60]을 반환합니다. 통계와 인기 목록은 각각 새 Stream을 소비하므로 통계 객체에서 필터 결과를 재사용한 것이 아닙니다.


반복문과 Stream 비교

성능에 민감한 수치 반복문은 기본형 for가 JIT 최적화와 단순한 메모리 접근에서 유리할 수 있습니다.

박싱된 Stream<Integer>에는 래퍼 객체와 언박싱 비용이 들며 기본형 IntStream은 이를 줄입니다.

그러나 가독성, 분기 구조, 원본 타입, 벡터화 가능성, JIT 예열 상태에 따라 실제 차이는 달라집니다.

실제 경과 시간 측정 한 번과 System.currentTimeMillis만으로 마이크로벤치마크의 결론을 내리지 않습니다.

JMH처럼 예열, fork, 죽은 코드 제거 방지를 제공하는 도구를 쓰고 실제 입력 크기를 반영합니다.

데이터가 작으면 차이가 운영 지연 시간에 의미 없을 수 있습니다.


forEach의 순서와 부수 효과

순차 예제에서 forEach가 만나는 순서대로 실행되더라도 병렬 실행의 순서 보장으로 확대하지 않습니다.

forEachOrdered는 순서를 유지하지만 병렬 이득을 줄일 수 있습니다.

결과 컬렉션이 필요하면 부수 효과를 일으키는 add 대신 collect를 사용합니다.

forEach 실행 중 예외가 발생하면 앞선 원소의 부수 효과는 이미 실행됐습니다.

원자적인 일괄 처리가 필요하면 먼저 불변 결과를 만들고 트랜잭션 경계에서 적용합니다.


최종 연산 결과의 API 반환

메서드가 Stream을 반환하면 호출자가 자원 수명과 일회 소비 책임을 떠안습니다.

메모리 안의 순수한 원본에는 유용할 수 있지만 파일이나 데이터베이스 커서라면 소유자가 최종 연산까지 수행하는 편이 안전합니다.

결과가 클 때는 결과 구체화 비용과 역압력 요구 사항을 별도로 설계합니다.


연습 문제

int 점수 목록에서 60점 이상만 대상으로 count, 평균, 최댓값을 하나의 record에 담으세요.

통과 점수가 없으면 평균과 최댓값을 0으로 정합니다.

정답과 빈 상태 처리

한 번의 summaryStatistics 최종 연산으로 필요한 통계를 모은 뒤, 외부에 반환할 Summary를 만듭니다.

exercise/PassingScoreSummarySolution.java
import java.util.IntSummaryStatistics;
import java.util.List;

public final class PassingScoreSummarySolution {
    private record Summary(long count, double average, int maximum) {}

    public static void main(String[] args) {
        IntSummaryStatistics stats =
                List.of(55, 60, 78, 92, 40).stream()
                        .mapToInt(Integer::intValue)
                        .filter(score -> score >= 60)
                        .summaryStatistics();
        Summary summary =
                stats.getCount() == 0
                        ? new Summary(0, 0.0, 0)
                        : new Summary(stats.getCount(), stats.getAverage(), stats.getMax());
        System.out.println(summary);
    }
}

통과 점수가 없을 때 최댓값 경계 상수를 외부 결과로 내보내지 않는다

55·60·78·92·40에서 통과한 값 60·78·92를 누산해 count 3/sum 230/max 92를 얻고 빈 경우에는 별도 정책값을 반환합니다. 선택·통계·최종화의 값 전환을 보여 줄 수 있습니다.

통과 점수가 없을 때 최대값 경계 상수를 외부 결과로 내보내지 않는다

PassingScoreSummarySolution의 통계 누산 결과와 반환 Summary를 구분합니다.

내부 통계와 반환 정책

통과 점수가 없을 때 최대값 경계 상수를 외부 결과로 내보내지 않는다

count가 0이면 빈 통계의 max 경계값을 내보내지 않습니다. 반환 Summary의 평균과 최대값은 0으로 정합니다.

count로 빈 통과 집합을 구분해 외부 통계 정책을 적용한다선별된60,78,92를누산하면count3sum230max92이고count0조건이아니어서외부Summary3,230/3,92로간다. 다른입력에서통과값0개면같은count분기의예경로가외부0,0.0,0으로끝난다. 원입력이동시에두결과가되는것이아니고통계모양과외부부재정책의6노드5간선이다. 약76.67은표시를반올림한값이며실제double이두자리로저장된것이아니다. 모바일도동일분기이다.점수 입력55·60·78·92·4060 이상 선별60·78·92통계 누산count 3 · sum 230max 92count가 0인가?빈 통과 결과0 · 0.0 · 0정상 통과 결과3 · 약76.67 · 92예아니오
count로 빈 통과 집합을 구분해 외부 통계 정책을 적용한다선별된60,78,92를누산하면count3sum230max92이고count0조건이아니어서외부Summary3,230/3,92로간다. 다른입력에서통과값0개면같은count분기의예경로가외부0,0.0,0으로끝난다. 원입력이동시에두결과가되는것이아니고통계모양과외부부재정책의6노드5간선이다. 약76.67은표시를반올림한값이며실제double이두자리로저장된것이아니다. 모바일도동일분기이다.점수 입력55·60·78·92·4060 이상 선별60·78·92통계 누산count 3 · sum 230max 92count가0인가?빈 통과 결과0 · 0.0 · 0정상 통과 결과3 · 약76.67 · 92예아니오

IntSummaryStatistics

60, 78, 92
count 3 · sum 230 · max 92
통과 점수 없음
count 0 · sum 0 · max Integer.MIN_VALUE

반환 Summary

60, 78, 92
count 3 · average 약 76.67 · maximum 92
통과 점수 없음
count 0 · average 0.0 · maximum 0

첫 행은 main의 고정 입력을 계산한 값이고, 둘째 행은 빈 통과 집합에 대한 코드 분기입니다. 빈 입력을 따로 실행한 기록은 아닙니다.

이 예의 조건

  • 약 76.67은 표시할 때 반올림한 값이며 double 값을 소수 두 자리로 저장한다는 뜻이 아닙니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2선별된 60,78,92를 누산하면 count 3, sum 230, max 92이고 count 0 조건이 아니어서 외부 Summary 3,230/3,92로 간다. 다른 입력에서 통과한 값이 0개면 같은 count 분기의 예 경로가 외부 0,0.0,0으로 끝난다. 원 입력이 동시에 두 결과가 되는 것이 아니라 통계의 형태와 외부 부재 정책을 6개 노드와 5개 간선으로 나타낸다. 약 76.67은 표시를 반올림한 값이며 실제 double이 두 자리로 저장된 것이 아니다. 모바일도 동일한 분기이다.
143 · 약 76.67 · 92
원 도표의 설명과 주석
구분내용
설명선별된 60,78,92를 누산하면 count 3, sum 230, max 92이고 count 0 조건이 아니어서 외부 Summary 3,230/3,92로 간다. 다른 입력에서 통과한 값이 0개면 같은 count 분기의 예 경로가 외부 0,0.0,0으로 끝난다. 원 입력이 동시에 두 결과가 되는 것이 아니라 통계의 형태와 외부 부재 정책을 6개 노드와 5개 간선으로 나타낸다. 약 76.67은 표시를 반올림한 값이며 실제 double이 두 자리로 저장된 것이 아니다. 모바일도 동일한 분기이다.
전체 읽기 정보통과 점수가 없을 때 최댓값 경계 상수를 외부 결과로 내보내지 않는다 통과 점수가 없을 때 최댓값 경계 상수를 외부 결과로 내보내지 않는다 PassingScoreSummarySolution의 통계 누산 결과와 반환 Summary를 구분합니다. 내부 통계와 반환 정책 통과 점수가 없을 때 최댓값 경계 상수를 외부 결과로 내보내지 않는다 count가 0이면 빈 통계의 max 경계값을 내보내지 않습니다. 반환 Summary의 평균과 최댓값은 0으로 정합니다. IntSummaryStatistics 60, 78, 92 count 3 · sum 230 · max 92 통과 점수 없음 count 0 · sum 0 · max Integer.MIN_VALUE 반환 Summary 60, 78, 92 count 3 · average 약 76.67 · maximum 92 통과 점수 없음 count 0 · average 0.0 · maximum 0 첫 행은 main의 고정 입력을 계산한 값이고, 둘째 행은 빈 통과 집합에 대한 코드 분기입니다. 빈 입력을 따로 실행한 기록은 아닙니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
점수 선별

55,60,78,92,40에서 60 이상을 선택합니다.

  • 통과: 60,78,92
  • 제외: 55,40
  • 통과한 값만 통계 누산에 반영합니다.
통계 누산

세 통과 값의 개수·합계·최대를 계산합니다.

  • count,sum: 3,230
  • max: 92
  • count가 통과한 원소 집합이 비어 있는지 판단하는 근거입니다.
외부 정책

count 3은 정상 Summary, 별도 count 0은 빈 정책을 적용합니다.

  • 정상: 3,230/3,92
  • 빈 입력 경로: 0,0.0,0
  • 원 입력이 동시에 두 결과가 되는 것이 아닙니다.

통과 점수가 없을 때 최댓값 경계 상수를 외부 결과로 내보내지 않는다

55·60·78·92·40에서 통과한 값 60·78·92를 누산해 count 3/sum 230/max 92를 얻고 빈 경우에는 별도 정책값을 반환합니다. 선택·통계·최종화의 값 전환을 보여 줄 수 있습니다.

통과 점수가 없을 때 최대값 경계 상수를 외부 결과로 내보내지 않는다

PassingScoreSummarySolution의 통계 누산 결과와 반환 Summary를 구분합니다.

내부 통계와 반환 정책

통과 점수가 없을 때 최대값 경계 상수를 외부 결과로 내보내지 않는다

count가 0이면 빈 통계의 max 경계값을 내보내지 않습니다. 반환 Summary의 평균과 최대값은 0으로 정합니다.

count로 빈 통과 집합을 구분해 외부 통계 정책을 적용한다선별된60,78,92를누산하면count3sum230max92이고count0조건이아니어서외부Summary3,230/3,92로간다. 다른입력에서통과값0개면같은count분기의예경로가외부0,0.0,0으로끝난다. 원입력이동시에두결과가되는것이아니고통계모양과외부부재정책의6노드5간선이다. 약76.67은표시를반올림한값이며실제double이두자리로저장된것이아니다. 모바일도동일분기이다.점수 입력55·60·78·92·4060 이상 선별60·78·92통계 누산count 3 · sum 230max 92count가 0인가?빈 통과 결과0 · 0.0 · 0정상 통과 결과3 · 약76.67 · 92예아니오
count로 빈 통과 집합을 구분해 외부 통계 정책을 적용한다선별된60,78,92를누산하면count3sum230max92이고count0조건이아니어서외부Summary3,230/3,92로간다. 다른입력에서통과값0개면같은count분기의예경로가외부0,0.0,0으로끝난다. 원입력이동시에두결과가되는것이아니고통계모양과외부부재정책의6노드5간선이다. 약76.67은표시를반올림한값이며실제double이두자리로저장된것이아니다. 모바일도동일분기이다.점수 입력55·60·78·92·4060 이상 선별60·78·92통계 누산count 3 · sum 230max 92count가0인가?빈 통과 결과0 · 0.0 · 0정상 통과 결과3 · 약76.67 · 92예아니오

IntSummaryStatistics

60, 78, 92
count 3 · sum 230 · max 92
통과 점수 없음
count 0 · sum 0 · max Integer.MIN_VALUE

반환 Summary

60, 78, 92
count 3 · average 약 76.67 · maximum 92
통과 점수 없음
count 0 · average 0.0 · maximum 0

첫 행은 main의 고정 입력을 계산한 값이고, 둘째 행은 빈 통과 집합에 대한 코드 분기입니다. 빈 입력을 따로 실행한 기록은 아닙니다.

이 예의 조건

  • 약 76.67은 표시할 때 반올림한 값이며 double 값을 소수 두 자리로 저장한다는 뜻이 아닙니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2선별된 60,78,92를 누산하면 count 3, sum 230, max 92이고 count 0 조건이 아니어서 외부 Summary 3,230/3,92로 간다. 다른 입력에서 통과한 값이 0개면 같은 count 분기의 예 경로가 외부 0,0.0,0으로 끝난다. 원 입력이 동시에 두 결과가 되는 것이 아니라 통계의 형태와 외부 부재 정책을 6개 노드와 5개 간선으로 나타낸다. 약 76.67은 표시를 반올림한 값이며 실제 double이 두 자리로 저장된 것이 아니다. 모바일도 동일한 분기이다.
143 · 약 76.67 · 92
원 도표의 설명과 주석
구분내용
설명선별된 60,78,92를 누산하면 count 3, sum 230, max 92이고 count 0 조건이 아니어서 외부 Summary 3,230/3,92로 간다. 다른 입력에서 통과한 값이 0개면 같은 count 분기의 예 경로가 외부 0,0.0,0으로 끝난다. 원 입력이 동시에 두 결과가 되는 것이 아니라 통계의 형태와 외부 부재 정책을 6개 노드와 5개 간선으로 나타낸다. 약 76.67은 표시를 반올림한 값이며 실제 double이 두 자리로 저장된 것이 아니다. 모바일도 동일한 분기이다.
전체 읽기 정보통과 점수가 없을 때 최댓값 경계 상수를 외부 결과로 내보내지 않는다 통과 점수가 없을 때 최댓값 경계 상수를 외부 결과로 내보내지 않는다 PassingScoreSummarySolution의 통계 누산 결과와 반환 Summary를 구분합니다. 내부 통계와 반환 정책 통과 점수가 없을 때 최댓값 경계 상수를 외부 결과로 내보내지 않는다 count가 0이면 빈 통계의 max 경계값을 내보내지 않습니다. 반환 Summary의 평균과 최댓값은 0으로 정합니다. IntSummaryStatistics 60, 78, 92 count 3 · sum 230 · max 92 통과 점수 없음 count 0 · sum 0 · max Integer.MIN_VALUE 반환 Summary 60, 78, 92 count 3 · average 약 76.67 · maximum 92 통과 점수 없음 count 0 · average 0.0 · maximum 0 첫 행은 main의 고정 입력을 계산한 값이고, 둘째 행은 빈 통과 집합에 대한 코드 분기입니다. 빈 입력을 따로 실행한 기록은 아닙니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
점수 선별

55,60,78,92,40에서 60 이상을 선택합니다.

  • 통과: 60,78,92
  • 제외: 55,40
  • 통과한 값만 통계 누산에 반영합니다.
통계 누산

세 통과 값의 개수·합계·최대를 계산합니다.

  • count,sum: 3,230
  • max: 92
  • count가 통과한 원소 집합이 비어 있는지 판단하는 근거입니다.
외부 정책

count 3은 정상 Summary, 별도 count 0은 빈 정책을 적용합니다.

  • 정상: 3,230/3,92
  • 빈 입력 경로: 0,0.0,0
  • 원 입력이 동시에 두 결과가 되는 것이 아닙니다.

원래 통과 점수는 60·78·92라 count 3, 합 230, 평균 230/3, 최대 92입니다. 평균을 소수 두 자리로 표시하면 약 76.67이지만 record의 double 값이 처음부터 두 자리로 반올림된 것은 아닙니다. 내부 통계 객체와 외부의 부재 정책을 나눠 보세요.

독립 확인: 같은 >=60 규칙에 [60,60,59], [59], 빈 목록을 각각 대입합니다. 내부 count·sum·max와 반환 Summary(count,average,maximum)를 구하세요. count를 확인하지 않고 빈 통계의 max를 반환하면 어떤 숫자가 새어 나오나요?

풀이 확인

첫 입력은 count 2·sum 120·max 60으로 Summary(2,60.0,60)입니다. 뒤 두 입력은 통과값이 없어 count 0·sum 0·max=Integer.MIN_VALUE이고 반환은 모두 Summary(0,0.0,0)입니다. Integer.MIN_VALUE인 -2147483648은 실제 점수나 문제의 부재 정책 0이 아니므로 외부 최댓값으로 내보내지 않습니다. IntSummaryStatistics의 빈 상태는 min=Integer.MAX_VALUE, max=Integer.MIN_VALUE, average 0.0을 각각 명시합니다. 빈 IntStream.min()의 OptionalInt와 이 내부 경계값도 서로 다른 반환 계약입니다.

최종 연산을 선택할 때는 반환 원소 수, 빈 입력, 단락 평가, 순서, 기본형 특화 스트림을 확인합니다.

최종 연산은 파이프라인의 생명 주기를 끝내므로 필요한 모든 결과를 어떻게 한 번에 회수할지도 함께 설계해야 합니다.