본문 바로가기

멋쟁이사자처럼_부트캠프/Spring

[멋쟁이사자처럼 부트캠프 TIL 회고] 백엔드 부트캠프 13기: Java 40일차 스트림

2. 스트림(Stream)

스트림(Stream)은 컬렉션(List, Set 등) 데이터를 처리하는 방식 으로, 데이터를 필터링, 변환, 집계하는데 사용됩니다. Java 8부터 지원되며, 내부 반복을 사용하여 코드 가독성과 성능을 높입니다.

 

스트림의 특징

 연속적 처리: 데이터를 하나씩 처리하는 방식(파이프라인)
 중간 연산(Intermediate Operation): filter(), map(), sorted() 등
 최종 연산(Terminal Operation): collect(), forEach(), reduce() 등
 병렬 처리 가능: parallelStream()을 사용하면 병렬 연산 가능

람다식 & 스트림을 사용하면 좋은 점

 코드가 간결해진다.
 가독성이 좋아진다.
 병렬 처리 가능 -> 성능 향상
 변경 불가능(Immutable)한 데이터 처리에 적합

 

스트림 방식으로 짝수만 출력하는 Java 코드 예제

import java.util.List;

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

        // 짝수만 필터링하여 공백을 포함하여 출력
        numbers.stream()
            .filter(n -> n % 2 == 0) // 짝수 필터링
            .forEach(n -> System.out.print(n + " ")); // 공백 추가
    }
}
//실행결과
2 4 6 8 10

 

 

스트림이 쓰이는 케이스

1. 컬렉션 데이터의 처리 및 변환

 

필터링(Filter):
예를 들어, 리스트에서 특정 조건에 맞는 요소(예: 짝수, 특정 문자열 포함 등)만 골라내고 싶을 때

List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

 

매핑(Map):
데이터를 다른 형태로 변환할 때 (예: 객체의 특정 필드 추출, 값을 다른 형식으로 변환)

List<String> names = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());

 

정렬(Sorted):
스트림에서 제공하는 정렬 기능을 사용하여 데이터를 순서대로 정렬할 때

List<Integer> sortedNumbers = numbers.stream()
    .sorted()
    .collect(Collectors.toList());

 

2. 집계 및 요약 작업

합계, 평균, 최대/최소값 구하기:
스트림의 reduce(), sum(), average() 등의 메서드를 사용하여 집계 작업을 쉽게 처리할 수 있습니다.

int sum = numbers.stream()
    .reduce(0, Integer::sum);

 

3. 병렬 처리

병렬 스트림(Parallel Stream):
큰 데이터셋을 처리할 때, 내부적으로 여러 스레드를 활용하여 작업을 병렬로 수행할 수 있어 성능 향상이 가능합니다.

List<Integer> result = numbers.parallelStream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

 

4. 선언적 프로그래밍 스타일

가독성 및 유지보수:
반복문과 조건문을 이용해 로직을 작성하는 대신, 스트림 API를 사용하면 "무엇을 할 것인가"에 집중한 코드를 작성할 수 있어 가독성이 좋고 유지보수가 쉬워집니다.

// 명령형 스타일
List<Integer> result = new ArrayList<>();
for (Integer n : numbers) {
    if (n % 2 == 0) {
        result.add(n);
    }
}

// 선언적 스타일 (스트림)
List<Integer> result = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

 

5. 다양한 데이터 소스 처리

배열, I/O, 파일, 데이터베이스 등:
컬렉션뿐만 아니라 배열이나 I/O 스트림 등에서도 데이터를 읽고 처리하는 경우 스트림 API의 개념(파이프라인)을 활용할 수 있습니다.

 

결론

스트림은 데이터 소스를 효율적으로 처리할 필요가 있을 때, 특히 다음과 같은 경우에 사용됩니다:

  • 컬렉션 데이터에서 특정 조건의 요소를 필터링하거나,
  • 매핑하여 변환하고,
  • 정렬하거나,
  • 집계(reduce, sum 등)를 수행할 때,
  • 병렬 처리를 통해 성능을 개선하고 싶을 때,
  • 선언적 스타일로 코드를 작성하여 가독성을 높이고 싶을 때.

즉, 스트림은 "데이터 처리의 파이프라인" 역할을 하며, 복잡한 반복 로직을 단순하게 표현할 수 있게 도와줍니다.