Functional Programming - Stream API

정찬·2025년 9월 11일

Functional Programming

목록 보기
4/4
post-thumbnail

개요

저번 글에서는 함수형 인터페이스에 대해 간단히 살펴봤습니다.

이번에는 Stream에 대해 알아보겠습니다.

Stream이란?

Stream은 데이터 컬렉션을 다루는 새로운 방식을 제공합니다.

컬렉션이나 배열을 반복문으로 직접 돌리는 대신, 선언적이고 함수형 스타일로 데이터를 처리할 수 있게 해줍니다.

Stream의 가장 큰 특징은 아래와 같습니다.

  • 원본 데이터를 변경하지 않고, 새로운 Stream을 생성해서 다룸
  • 내부 반복을 사용하기 때문에 코드가 간결해짐
  • 필터링, 매핑, 정렬, 집계 등 다양한 연산을 지원

간단한 예제를 먼저 보겠습니다.

List<String> names = List.of("Alice", "Bob", "Charlie", "David");

names.stream()
     .filter(name -> name.length() > 3)
     .map(String::toUpperCase)
     .forEach(System.out::println);

// 출력: ALICE, CHARLIE, DAVID

기존에는 for문과 if문을 여러 줄 써야 했던 로직이, Stream을 사용하면 한 줄의 선언형 코드로 바뀌는 것을 확인할 수 있습니다.

Stream의 특징

Stream API는 보통 세 단계로 나눠볼 수 있습니다.

  1. 생성 (Source)

    • 컬렉션, 배열, 파일, 값에서 Stream을 생성합니다.
  2. 중간 연산 (Intermediate Operation)

    • filter, map, sorted 같은 변환 작업을 정의합니다.
    • lazy하게 동작하여 실제 실행되지 않습니다.
  3. 최종 연산 (Terminal Operation)

    • collect, forEach, reduce 같은 연산을 호출해야 Stream이 실행됩니다.

즉, 중간 연산만 계속 정의하면 아무 일도 일어나지 않고, 최종 연산을 호출하는 순간 파이프라인이 실행됩니다.

Stream 생성

Stream은 다양한 방법으로 만들 수 있습니다.

// 컬렉션
Stream<String> s1 = List.of("a", "b", "c").stream();

// 배열
Stream<Integer> s2 = Arrays.stream(new Integer[]{1, 2, 3});

// 값 직접
Stream<String> s3 = Stream.of("x", "y", "z");

// 무한 스트림
Stream<Integer> s4 = Stream.iterate(0, n -> n + 2);

중간 연산

filter

조건에 맞는 요소만 통과시킵니다.

List<Integer> nums = List.of(1, 2, 3, 4, 5);

nums.stream()
    .filter(n -> n % 2 == 0)
    .forEach(System.out::println);

// 출력: 2, 4

map

요소를 다른 값으로 변환합니다.

List<String> names = List.of("java", "python", "go");

names.stream()
     .map(String::toUpperCase)
     .forEach(System.out::println);

// 출력: JAVA, PYTHON, GO

sorted

요소를 정렬합니다.

List<String> names = List.of("Charlie", "Alice", "Bob");

names.stream()
     .sorted()
     .forEach(System.out::println);

// 출력: Alice, Bob, Charlie

최종 연산

collect

Stream 결과를 다시 컬렉션으로 모읍니다.

List<String> result =
    names.stream()
         .filter(n -> n.length() > 3)
         .collect(Collectors.toList());

forEach

각 요소를 소비합니다.

names.stream()
     .forEach(System.out::println);

reduce

모든 요소를 하나로 합칩니다.

int sum = IntStream.rangeClosed(1, 5)
                   .reduce(0, Integer::sum);

System.out.println(sum); // 15

병렬 스트림

Stream은 간단히 병렬 처리도 지원합니다.

int total = IntStream.rangeClosed(1, 1_000_000)
                     .parallel()
                     .sum();

System.out.println(total); // 500000500000

CPU 코어를 활용하여 데이터가 많을 때 성능 이점을 볼 수 있습니다.

마치며

Stream은 컬렉션을 다루는 방식을 획기적으로 바꿔줍니다.

for문으로 반복하면서 조건문을 작성하던 방식보다 훨씬 간결하고 선언적인 코드를 작성할 수 있습니다.

특히, 중간 연산과 최종 연산의 파이프라인 구조를 이해하면 코드가 읽기 쉬워지고 유지보수도 편리해집니다.

0개의 댓글