Develop/Java

[Java] Stream 으로 간결하고 효율적인 데이터 처리하기

DevPi 2024. 9. 5. 09:37
반응형

Java 8에서 도입된 Stream API는 데이터를 처리하고 변환하는 방식을 혁신적으로 개선했습니다. 컬렉션이나 배열의 데이터를 함수형 프로그래밍 스타일로 처리할 수 있도록 지원하며, 복잡한 데이터 처리 로직을 간결하고 직관적으로 구현할 수 있게 합니다. 이번 포스트에서는 Java Stream의 개념과 주요 기능을 살펴보고, 다양한 예시를 통해 어떻게 활용할 수 있는지 알아보겠습니다.


Java Stream이란?

Java Stream은 데이터 소스의 요소들을 추상화하여 일련의 연산(필터링, 매핑, 집계 등)을 처리할 수 있는 API입니다. 스트림을 사용하면 반복문 없이 선언적이고 함수형 프로그래밍 스타일로 데이터를 처리할 수 있습니다. 예를 들어, 리스트의 요소를 필터링하거나, 맵핑하여 변환하고, 집계하는 작업 등을 손쉽게 구현할 수 있습니다.

 

Java Stream의 주요 특징

  1. 선언적 스타일 : 스트림을 사용하면 반복문 없이 고차 함수(filter, map, reduce 등)를 사용하여 선언적으로 코드를 작성할 수 있습니다.
  2. 함수형 프로그래밍 : 람다 표현식과 함께 사용하여 간결하고 가독성 높은 코드를 작성할 수 있습니다.
  3. 지연 처리(Lazy Evaluation) : 스트림 연산은 필요할 때만 수행되므로 성능 최적화를 도와줍니다.
  4. 병렬 처리 : 병렬 스트림을 사용하면 멀티코어 프로세서를 활용한 병렬 처리가 가능합니다.

스트림의 구조

스트림은 데이터 처리 작업을 세 가지 단계로 나눕니다:

  • 생성 : 스트림을 데이터 소스(컬렉션, 배열, I/O 등)로부터 생성합니다.
  • 중간 연산 : 스트림을 변환하거나 필터링하는 연산으로, 이 단계에서는 새로운 스트림이 생성됩니다. 중간 연산은 지연되어 필요할 때 실행됩니다.
  • 종결 연산 : 스트림의 최종 결과를 도출하는 연산입니다. 종결 연산이 호출되면 스트림은 소비되고 더 이상 사용할 수 없습니다.

Java Stream 사용 예시

Java Stream은 간단한 작업부터 복잡한 데이터 변환까지 다양한 시나리오에서 활용할 수 있습니다. 아래 예시를 통해 Java Stream의 기본적인 사용법을 알아봅시다.

 

1. 리스트에서 짝수만 필터링하여 출력하기

스트림을 사용하여 리스트에서 짝수만 필터링하고 출력하는 간단한 예시입니다.

import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        numbers.stream()                      // 스트림 생성
                .filter(n -> n % 2 == 0)      // 중간 연산: 필터링
                .forEach(System.out::println); // 종결 연산: 출력
    }
}

위 코드는 filter 중간 연산을 사용하여 짝수만을 필터링하고, forEach 종결 연산을 통해 결과를 출력합니다.

 

2. 문자열 리스트를 대문자로 변환하고 정렬하기

문자열 리스트의 모든 요소를 대문자로 변환하고 정렬하는 예시입니다.

import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("alice", "bob", "charlie", "david");

        names.stream()
                .map(String::toUpperCase)   // 중간 연산: 대문자로 변환
                .sorted()                   // 중간 연산: 정렬
                .forEach(System.out::println); // 종결 연산: 출력
    }
}

map 중간 연산을 통해 문자열을 대문자로 변환하고, sorted 중간 연산으로 알파벳 순서대로 정렬합니다.

 

3. 리스트의 숫자 합계 계산

스트림을 사용하여 리스트의 숫자를 모두 더하는 방법입니다.

import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        int sum = numbers.stream()
                .reduce(0, Integer::sum); // 종결 연산: 합계 계산

        System.out.println("Sum: " + sum);
    }
}

Java Stream의 주요 연산들

Java Stream에서 자주 사용되는 주요 연산들을 소개합니다. 스트림의 연산은 크게 중간 연산과 종결 연산으로 나뉩니다.

 

주요 중간 연산

  • filter(Predicate<T> predicate) : 조건에 맞는 요소만 선택합니다.
  • map(Function<T, R> mapper) : 요소를 다른 형태로 변환합니다.
  • sorted(Comparator<T> comparator) : 요소를 정렬합니다.
  • distinct() : 중복된 요소를 제거합니다.
  • limit(long maxSize) : 요소의 개수를 제한합니다.
  • skip(long n) : 처음 n개의 요소를 건너뜁니다.

 

주요 종결 연산

  • forEach(Consumer<T> action) : 각 요소에 대해 작업을 수행합니다.
  • collect(Collector<T, A, R> collector) : 스트림의 결과를 컬렉션, 리스트, 맵 등으로 변환합니다.
  • reduce(BinaryOperator<T> accumulator) : 요소를 하나의 값으로 줄입니다.
  • count() : 요소의 개수를 반환합니다.
  • anyMatch(Predicate<T> predicate), allMatch(Predicate<T> predicate), noneMatch(Predicate<T> predicate) : 조건에 따른 요소 검사

마무리

Java Stream은 데이터를 다루는 방식을 간결하고 효율적으로 만들어주는 강력한 기능입니다. 스트림을 사용하면 데이터 처리가 더욱 직관적이고 선언적으로 이루어지며, 성능 최적화와 병렬 처리의 이점을 누릴 수 있습니다. 이번 포스트에서는 Java Stream의 개념과 주요 기능을 살펴보았는데요, 이를 활용하여 더 읽기 쉽고 유지 관리가 쉬운 코드를 작성해 보세요!

반응형