Stream이란?
Stream은 자바 8에서 추가된 새로운 데이터 처리 추상화 개념으로, 함수형 프로그래밍을 지원한다.
Stream vs Collection
Collection
- 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조이다. 따라서 컬렉션 안의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다.
- 외부 반복을 통해 사용자가 직접 반복 작업을 수행하여 요소를 가져온다. 즉, 개발자는 명시적으로 요소를 가져오기 위해 Iterator 또는 인덱스로 컬렉션을 순회해야 한다.
Stream
- 요청할 때만 요소를 계산하는 고정된 자료구조이다. 스트림에 요소를 추가하거나 삭제할 수 없으며, 사용자가 요청할 때만 스트림에서 요소를 추출한다.
- 내부 반복을 사용하므로, 사용자는 추출되는 요소만 선언하면 된다. 계산식을 스트림으로 미리 정의하고, 람다식 등으로 JVM에 넘겨서 스트림 내부에서 알아서 반복 처리를 한다.
- 스트림은 생산자-소비자 관계를 형성한다. 즉, 데이터를 생산하고 이를 소비하는 방식으로 처리한다.
- 스트림은 파이프-필터 기반으로 동작하며, 연산(filter, sorted, map 등)을 끼워 넣어 파이프라인처럼 연결하여 데이터를 처리한다. 스트림은 무한 연속 데이터 흐름 API 성격을 가지고 있어 데이터 처리를 효율적으로 수행할 수 있다.
외부 반복 & 내부 반복
- 외부 반복(External Iteration)
- 개발자가 명시적으로 반복 작업을 수행하는 방식(for 루프, while 루프)
- 내부 반복(Internal Iteration)
- 데이터 처리 작업을 데이터 구조 자체에게 위임하는 방식
- 데이터 처리 작업을 보다 추상화하고 단순화할 수 있으며, 병렬 처리를 활용하여 성능을 향상시키는 데 도움이 된다.
Stream 연산
스트림은 데이터를 처리하기 위한 연산을 중간 연산과 최종 연산으로 구분할 수 있다.
중간 연산(Intermediate Operations)
1. Filter
주어진 조건을 만족하는 요소를 선택하여 새로운 스트림을 생성한다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// evenNumbers: [2, 4, 6]
2. distinct
중복된 요소를 제거한 새로운 스트림을 생성한다.
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
// distinctNumbers: [1, 2, 3, 4, 5]
3. limit
스트림에서 처음 n개의 요소로 제한된 새로운 스트림을 생성한다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> limitedNumbers = numbers.stream()
.limit(3)
.collect(Collectors.toList());
// limitedNumbers: [1, 2, 3]
4. skip
스트림에서 처음 n개의 요소를 건너뛴 나머지 요소로 구성된 새로운 스트림을 생성한다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> skippedNumbers = numbers.stream()
.skip(2)
.collect(Collectors.toList());
// skippedNumbers: [3, 4, 5]
5. map
각 요소를 주어진 함수를 적용하여 새로운 값을 반환하는 스트림을 생성한다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(name -> name.length())
.collect(Collectors.toList());
// nameLengths: [5, 3, 7]
6. flatMap
스트림의 콘텐츠로 매핑하고 결과를 평면화된 스트림으로 반환한다.
List<List<Integer>> nestedNumbers = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4, 5));
List<Integer> flatNumbers = nestedNumbers.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// flatNumbers: [1, 2, 3, 4, 5]
7. sorted
요소를 정렬하여 새로운 스트림을 반환한다. 데이터의 종류에 따라 Comparator가 필요할 수 있다.
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
// sortedNumbers: [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eva");
// 문자열 길이를 기준으로 오름차순으로 정렬
List<String> sortedNamesByLength = names.stream()
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList());
// sorted: [Bob, Eva, Alice, David, Charlie]
// 문자열을 알파벳 역순으로 정렬
List<String> sortedNamesAlphabeticallyReverse = names.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// sorted: [Eva, David, Charlie, Bob, Alice]
최종 연산(Terminal Operations)
1. reduce
스트림의 모든 요소를 처리하여 값을 도출한다. 두 개의 인자를 가질 수 있으며, 첫 번째 인자는 초기값 또는 identity 값이고, 두 번째 인자는 BinaryOperator이다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
// sum: 15
2. collect
스트림을 사용하여 데이터를 컬렉션(리스트, 맵 등)으로 수집한다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> collectedNumbers = numbers.stream()
.collect(Collectors.toList());
// collectedNumbers: [1, 2, 3, 4, 5]
[참고] https://gyoogle.dev/blog/computer-language/Java/Stream.html
'Languages > Java' 카테고리의 다른 글
[Java] 컴포지션 (Composition) (0) | 2023.09.14 |
---|---|
[Java] Error & Exception (0) | 2023.09.07 |
[Java] Garbage Collection (0) | 2023.09.06 |
[Java] JVM (Java Virtual Machine) (0) | 2023.09.06 |
[Java] 고유 락 (Intrinsic Lock) (0) | 2023.08.31 |