Stream이란?
자바에서 대량의 데이터를 가지고 있는 Collection 자료들의 효율적인 병렬처리를 위해서 주어지는 API이다.
Stream은 크게 3가지 구조로 나눌 수 있다.
- Stream의 생성 (Collection ➡️ Stream)
- 중간 연산 (Stream ➡️ Stream)
- 최종 소비 연산 (Stream ➡️ Collection)
1. Stream 생성
Stream은 무조건 Collection의 구현체들로부터 생성된다.
그래서 아래와 같은 생성의 모습을 보인다.
List<String> list = Arrays.asList("a", "b", "c"); // ArrayList로 구현
Stream<String> stream = list.stream();
2. 중간 연산
- 스트림을 변환하거나 필터링 하는 작업을 수행한다.
- 항상 Stream을 반환한다.
아래는 중간 연산에 사용되는 메서드들의 종류이다.
filter : 각 element에 대해 boolean값이 참인 것만을 사용.
따라서 boolean을 반환하는 predicate의 함수형 인터페이스를 인자로 받는다.
Stream<T> filter(Predicate<? super T> predicate)
// predicate는 T(타입) -> Boolean 을 반환하는 functional interface
Stream<String> stream = Stream.of("apple", "banana", "cherry");
Stream<String> filteredStream = stream.filter(s -> s.startsWith("a"))
map : 각 element를 다른 타입으로 바꾼다.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
// map의 문서를 살펴보면 위와 같이 선언되어 있고,
// <R>라는 타입을 사용하겠다는 선언과 함께
// Function이라는 함수형 인터페이스를 인자로 받고 있다.
// T또는 T상위를 받아 R또는 R의 하위를 리턴하는 mapper이다.
Stream<String> stream = Stream.of("apple", "banana", "cherry");
Stream<Integer> lengthStream = stream.map(String::length);
flatMap : 스트림의 각 요소를 Stream으로 변환하고, 중첩된 스트림을 하나의 스트림으로 통합.
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
// T를 받아 Stream<R>로 반환하는 Function 이라는 함수형 인터페이스를 인자로 받는다
Stream<List<String>> stream = Stream.of(Arrays.asList("a", "b"), Arrays.asList("c", "d"));
Stream<String> flatMappedStream = stream.flatMap(List::stream); // "a", "b", "c", "d"로 병합
List::stream
(list -> list.stream()) //위와 아래는 같은 형태이다.
flat, 말그대로 평평하게 하는 느낌이라고 볼 수 있다.
Stream의 Stream으로 중첩된 상태를 만든 후, 하나의 단일 Stream으로 통합하는 느낌이다.
혹은 2레이어짜리 stream을 1레이어짜리 stream으로 재구성하는 느낌이다.
distinct : 중복된 요소를 제거한 스트림을 반환한다.
Stream<T> distinct()
Stream<String> stream = Stream.of("apple", "banana", "apple");
Stream<String> distinctStream = stream.distinct(); // "apple", "banana"만 포함
sorted : 순서를 정렬한 스트림을 반환한다.
Stream<T> sorted()
Stream<String> stream = Stream.of("banana", "apple", "cherry");
Stream<String> sortedStream = stream.sorted(); // "apple", "banana", "cherry"로 정렬
Stream<T> sorted(Comparator<? super T> comparator)
Stream<String> stream = Stream.of("banana", "apple", "cherry");
Stream<String> sortedStream = stream.sorted(Comparator.reverseOrder()); // 역순 정렬
comparator라는 비교자를 통해서 sort를 진행한다.
Comparator?
이는 비교를 해주는 함수형 인터페이스로, 두 개의 값을 비교해서 숫자인 값을 반환한다.
첫 번째 비교자가 두 번째 비교자보다 작아서 음수를 반환하게 될 경우는 오름차순이 된다.
굳이 comparator를 사용하지 않아도, 직접 람다 함수를 통해서 구현 할 수도 있다.
가령, 위의 예시에서 객체의 길이의 역순으로(내림차순으로) 정렬하고 싶다면
Stream<String> stream = Stream.of("banana", "apple", "cherry");
Stream<String> sortedStream = stream.sorted((a, b) -> b.length() - a.length()); // 역순 정렬
와 같이 람다함수로 할 수도 있다.
다만 compartor가 표준화된 함수형 인터페이스이기 때문에, 굳이 람다함수로 대체하지 않아도 된다.
int maxLocation = cars.stream()
.max(Comparator.comparingInt(Car::getCurrentLocation))
.map(Car::getCurrentLocation)
.orElseThrow(() -> new IllegalStateException("No cars available"));
// 위와 아래는 같은 연산을 하고, 같은 결과를 반환한다.
int maxLocation = cars.stream()
.max((a, b) -> a.getCurrentLocation() - b.getCurrentLocation())
.map(each -> each.getCurrentLocation())
.orElseThrow(() -> new IllegalStateException("No cars available"));
// 이례적으로 아래처럼 순서를 바꿔도 똑같다는 것을 알 수 있다.
int maxLocation = cars.stream()
.map(each -> each.getCurrentLocation())
.max((a, b) -> a - b)
.orElseThrow(() -> new IllegalStateException("No cars available"));
'연재작 > 프로그래밍 언어' 카테고리의 다른 글
Java - Optional 뽀개기 (1) (0) | 2024.10.11 |
---|---|
Java - Stream 뽀개기 (2) (2) | 2024.10.06 |
monad 패턴, optional, promise, stream (1) | 2024.10.05 |
Java Collection, Map에 내재되어 있는 수학 (0) | 2024.09.27 |
수학적 철학을 가미한 함수형 프로그래밍 (1) | 2024.08.31 |