본문 바로가기
쿤즈 Dev/Java

[자바 스트림 12] reduce로 누적 계산 — 집계 로직을 직접 설계한다

by Koonz:) 2026. 10. 28.
728x90
요약: reduce는 스트림의 요소를 하나의 값으로 접어(fold) 합치는 범용 집계 연산입니다. sum()·count() 같은 전용 집계가 "미리 만들어진 reduce"라면, reduce는 합·곱·연결·최댓값 등 임의의 누적 규칙을 직접 작성할 수 있는 원형입니다. 이 글에서 항등값(identity)의 의미, 2-인자 vs 1-인자(Optional) 버전, 그리고 면접 포인트인 "항등값을 왜 주는가"를 정리합니다.

filter·map으로 데이터를 가공했다면, 결국 하나의 결과로 모아야 할 때가 많습니다. "전체 합", "총 곱", "모든 문자열 연결" 처럼요. 기초편 6편의 sum()은 합 전용이었지만, "요소 사이에 커스텀 연산을 끼워 누적" 하려면 reduce가 필요합니다.

 

reduce의 핵심은 초깃값(항등값)에서 시작해, 누산기(accumulator)로 요소를 하나씩 접어 넣는다는 것입니다. 합이면 0에서 시작해 +, 곱이면 1에서 시작해 *. 이 "시작값 + 결합 규칙" 패턴만 이해하면 어떤 집계든 표현할 수 있습니다.

 

자바 스트림 reduce 직접 설계하는 누적 집계 합 곱 최댓값

 

문제 정의

 

문제. 정수 리스트 [1,2,3,4,5] 에 대해 ① 합, ② 곱, ③ 최댓값을 reduce로 구하고, ④ 문자열 리스트를 -로 연결하라.

 

  • 입력: List<Integer> nums = [1, 2, 3, 4, 5], List<String> words = ["Java", "Stream", "API"]
  • 기대 출력: 합 15, 곱 120, 최댓값 5, 연결 Java-Stream-API

 

전통 방식(for-loop) — 그리고 스트림이 나은 이유

 

import java.util.List;

public class Traditional {
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4, 5);

        int sum = 0;                 // 초깃값(항등값)
        for (int n : nums) {
            sum = sum + n;           // 누적 규칙
        }
        System.out.println(sum);
    }
}

 

for 루프의 "누적 변수 + 반복 갱신" 패턴, 익숙하시죠? 바로 이것이 reduce가 추상화한 구조입니다. 초깃값 0 과 결합 규칙 sum + n 두 가지만 바꾸면 합이 곱도, 연결도 됩니다. reduce는 그 두 조각만 인자로 받습니다.

 

스트림 해법 — reduce

 

자바 스트림 reduce 누적 집계 항등값 accumulator

 

import java.util.List;
import java.util.Optional;

public class ReduceExample {
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4, 5);

        // ① 합 — 항등값 0, 결합 규칙 더하기
        int sum = nums.stream().reduce(0, Integer::sum);
        System.out.println(sum);

        // ② 곱 — 항등값 1, 결합 규칙 곱하기
        int product = nums.stream().reduce(1, (a, b) -> a * b);
        System.out.println(product);

        // ③ 항등값 없는 버전 → Optional 반환
        Optional<Integer> max = nums.stream().reduce(Integer::max);
        System.out.println(max.get());

        // ④ 문자열을 '-'로 연결 (빈 문자열에서 시작)
        List<String> words = List.of("Java", "Stream", "API");
        String joined = words.stream()
            .reduce("", (acc, w) -> acc.isEmpty() ? w : acc + "-" + w);
        System.out.println(joined);
    }
}

 

한 줄씩 설명

 

  • reduce(0, Integer::sum) — 첫 인자 0이 항등값(초깃값), 둘째 인자 Integer::sum이 누산기입니다. 0부터 시작해 요소를 차례로 더해 15가 됩니다.
  • **reduce(1, (a, b) -> a b) — 곱은 항등값이 1* 이어야 합니다(0으로 시작하면 전부 0). 11234*5 = 120.
  • reduce(Integer::max) — 항등값을 생략하면 1-인자 버전이 되어 Optional<Integer>를 반환합니다. 스트림이 비면 "결과 없음"일 수 있기 때문이죠. max.get()으로 5를 꺼냅니다.
  • reduce("", (acc, w) -> ...) — 문자열 연결도 reduce로 가능합니다. 다만 구분자 연결은 다음 편에서 볼 Collectors.joining 이 훨씬 간결합니다(여기선 원리 이해용).

 

실행 결과

 

15
120
5
Java-Stream-API

 

변형·함정·주의

 

  • ⚠ 항등값(identity)은 "연산에 영향을 주지 않는 값". 합은 0, 곱은 1, 문자열 연결은 "". 곱에 0을 주면 결과가 전부 0이 되는 대표적 실수입니다.
  • 1-인자 vs 2-인자. 항등값을 주면 결과 타입이 그대로(int 등)지만, 항등값을 생략하면 빈 스트림 대비로 Optional 을 반환합니다. 빈 스트림 가능성이 있으면 1-인자 버전이 안전합니다.
  • 전용 집계가 있으면 그걸 써라. 단순 합·평균은 mapToInt(...).sum() / average()가 더 읽기 쉽고 빠릅니다. reduce는 표준 집계로 표현 안 되는 커스텀 규칙에 쓰는 것이 정석입니다.
  • 병렬 스트림의 3-인자 reduce. reduce(identity, accumulator, combiner) 형태는 병렬 처리 시 부분 결과를 합치는 combiner를 받습니다. 병렬에서는 항등값·결합 규칙이 결합법칙(associative) 을 만족해야 결과가 정확합니다.

 

관련 면접 질문

 

  • Q1. reduce에서 항등값(identity)의 역할은?

 

→ 누적의 초깃값이자, 빈 스트림일 때 반환되는 기본값입니다. 연산의 항등원이어야 하며(합=0, 곱=1), 병렬 처리 시 각 조각의 시작값으로도 쓰입니다.

 

  • Q2. 항등값 없는 reduce가 Optional을 반환하는 이유는?

 

→ 초깃값이 없으면 빈 스트림일 때 내놓을 값이 없기 때문입니다. 그래서 "값이 없을 수 있음"을 Optional로 표현합니다.

 

시리즈 다시보기

 

 

누적의 원리를 익혔으니, 다음 편부터는 Collectors의 진짜 핵심이 시작됩니다. 먼저 코테·면접 출제율 1위 groupingBy로 그룹핑입니다.

댓글