김영한님의 김영한의 실전 자바 – 고급 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은 정렬된 스트림에서 유용하지만, 정렬되지 않는 경우 예측하기 어렵다