Java Stream 중간 연산

김영한님의 김영한의 실전 자바 – 고급 3편, 람다, 스트림, 함수형 프로그래밍 내용 중 일부로 실무에서 자주 사용될 법한 Stream의 중간 연산 종류들이다.

Stream에서의 중간 연산

중간 연산(Intermediate Operation)은 스트림 파이프라인에서 데이터를 변환, 필터링, 정렬하는 단계로, 여러 연산을 연결해 원하는 형태로 가공할 수 있다. 결과는 즉시 생성되지 않고, 최종 연산이 실행될 때 지연 처리된다

중간 연산의 지연 처리(Lazy evaluation)는 필요한 데이터만, 필요한 순간에 처리하기 때문에 메모리 사용과 연산 시간을 크게 줄일 수 있다는 점에서 성능 최적화에 크게 기여한다

1. filter

조건에 맞는 요소만 추려낸다

List<Integer> numbers = List.of(1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10);

numbers.stream()
    .filter(n -> n % 2 == 0)
    .forEach(n -> System.out.print(n + " "));

// 짝수만 선택
// 2 2 4 6 8 10 

2. map

요소를 다른 형태로 변환한다

List<Integer> numbers = List.of(1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10);

numbers.stream()
    .map(n -> n * 2)
    .forEach(n -> System.out.print(n + " "));

// 각 숫자를 곱셈
// 2 4 4 6 8 10 10 12 14 16 18 20

3. distinct

중복 요소를 제거한다 (MySQL에서도 사용해본 키워드)

List<Integer> numbers = List.of(1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10);

numbers.stream()
    .distinct()
    .forEach(n -> System.out.print(n + " "));

// 중복 제거
// 1 2 3 4 5 6 7 8 9 10

4. sorted

스트림의 요소를 정렬한다

Stream<Integer> sortedStream = Stream.of(6, 5, 2, 3, 8, 1, 7, 9, 4);

sortedStream.sorted()
    .forEach(n -> System.out.print(n + " "));

// 기본 정렬
// 1 2 3 4 5 6 7 8 9 

5. sorted (커스텀 Comparator)

스트림의 요소를 커스텀해서 정렬한다

Stream<Integer> sortedCustomStream = Stream.of(6, 5, 2, 3, 8, 1, 7, 9, 4);

sortedCustomStream
    .sorted(Comparator.reverseOrder())
    .forEach(n -> System.out.print(n + " "));

// 내림차순 정렬
// 9 8 7 6 5 4 3 2 1

6. peek

중간 단계에서 요소를 엿보는 용도로 사용한다. 데이터 변경을 하지 않고 주로 디버깅이나 로깅 용도로 사용된다

List<Integer> numbers = List.of(1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10);

numbers.stream()
    .peek(n -> System.out.print("before: " + n + ", "))
    .map(n -> n * n)
    .peek(n -> System.out.print("after: " + n + ", "))
    .limit(5)
    .forEach(n -> System.out.println("최종값: " + n));

// 동작 확인용
// before: 1, after: 1, 최종값: 1
// before: 2, after: 4, 최종값: 4
// before: 2, after: 4, 최종값: 4
// before: 3, after: 9, 최종값: 9
// before: 4, after: 16, 최종값: 16

7. limit

앞에서부터 N개의 요소만 추출한다

List<Integer> numbers = List.of(1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10);

numbers.stream()
    .limit(5)
    .forEach(n -> System.out.print(n + " "));

// 처음 5개 요소만
// 1 2 2 3 4 

8. skip

앞에서부터 N개의 요소를 건너뛰고, 나머지 요소로 스트림을 구성한다

List<Integer> numbers = List.of(1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10);

numbers.stream()
    .skip(5)
    .forEach(n -> System.out.print(n + " "));

// 처음 5개 요소를 건너뛰기
// 5 5 6 7 8 9 10

9. takeWhile (Java9 +)

조건에 만족하는 동안 요소를 가져온다. 조건이 처음으로 거짓이 되는 지점에서 스트림을 멈춘다. 스트림이 중간에 멈추기 때문에 원하는 목적을 빨리 달성하면 성능을 최적화할 수 있다

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 1, 2, 3);
numbers.stream()
    .takeWhile(n -> n < 5)
    .forEach(n -> System.out.print(n + " "));

// 5보다 작은 동안만 선택
// 1 2 2 3 4

10. dripWhile (Java9 +)

조건을 만족하는 동안 요소를 버린다. 조건이 처음으로 거짓이 되는 지점부터 스트림을 구성한다

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 1, 2, 3);
numbers.stream()
    .dropWhile(n -> n < 5)
    .forEach(n -> System.out.print(n + " "));

// 5보다 작은 동안 건너뛰기
// 5 1 2 3
  • 중간 연산은 스트림을 파이프라인 형태로 연결해 데이터를 가공하지만, 원본은 변하지 않는다. 이들은 지연 연산으로, 최종 연산이 실행될 때 실제 처리된다.
  • peek은 디버깅용으로만 쓰이며 값을 변경하거나 결과를 반환하지 않는다.
  • takeWhile, dropWhile은 정렬된 스트림에서 유용하지만, 정렬되지 않는 경우 예측하기 어렵다

출처 – 인프런 강의 중 김영한의 실전 자바 – 고급 3편, 람다, 스트림, 함수형 프로그래밍