Stream은 크게 3가지로 구성되었고 중간 연산 메소드까지 소개했었다.
3. 최종 연산 - Optional을 반환하는 경우(Optional로 가로채는)도 있으니 주의
Stream은 데이터들의 다양한 연산을 도와주는데,
그 중에는 연산을 했지만 값이 나오지 않거나 에러가 뜨는 연산도 존재할 수 있다.
이럴 가능성이 있는 메소드들은 Stream이 아니라 Optional로 반환을 한다.
종류를 설명하기 전에 최종 연산에 대해 간단히 설명하자면,
- stream을 소비하고 연산을 종료한다.
- 최종 연산이 호출되면 스트림을 더이상 사용할 수 없다.
해당 색깔의 method는 전부 다 Optional을 반환한다.
findFirst : 첫 번째 요소를 반환. stream이 비어있는 경우도 있기에 Optional을 반환한다.
Optional<T> findFirst()
Stream<String> stream = Stream.of("a", "b", "c");
Optional<String> first = stream.findFirst(); // Optional["a"] 반환
findAny : 임의의 요소를 반환. 병렬 스트림(parallelStream)에서 유용하다는데
Optional<T> findAny()
Stream<String> stream = Stream.of("a", "b", "c");
Optional<String> any = stream.findAny(); // Optional["a"] (또는 다른 임의의 요소) 반환
병렬 스트림(Parallel Stream)은 Java에서 대량의 데이터를 보다 효율적으로 처리하기 위해 여러 개의 쓰레드에서 작업을 분할하여 동시에 수행할 수 있는 스트림입니다. 병렬 스트림을 사용하면 데이터를 여러 개의 쓰레드로 나눠 병렬로 처리하여 성능을 향상시킬 수 있습니다. 특히, 데이터 양이 많거나 계산이 복잡할 때 병렬 스트림을 사용하면 멀티코어 CPU를 활용하여 실행 시간을 줄일 수 있습니다.
병렬 스트림의 주요 특징:
- 자동으로 쓰레드 분배: 병렬 스트림은 내부적으로 작업을 여러 쓰레드로 나누어 처리합니다. 개발자가 직접 쓰레드를 관리할 필요 없이, Java의 ForkJoinPool이 작업을 자동으로 병렬 처리합니다.
- 비결정성: 병렬 스트림은 여러 쓰레드가 동시에 작업을 수행하므로, 작업 순서가 보장되지 않습니다. 따라서 순서에 민감한 작업에는 병렬 스트림을 사용하지 않는 것이 좋습니다.
- 데이터의 병렬 처리: 스트림의 데이터 요소들을 분할하여 동시에 여러 쓰레드에서 작업을 처리하므로, 성능 향상이 가능합니다.
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.parallelStream()
.forEach(System.out::println);
list.stream()
.parallel()
.forEach(System.out::println); // 이 두 가지 방법으로 parallel을 사용한다.
min, max : 말 그대로 min, max 값을 반환 한다.
Optional<T> min(Comparator<? super T> comparator);
Optional<T> max(Comparator<? super T> comparator);
Optional<Integer> min = Stream.of(1, 2, 3).min(Integer::compare);
Optional<Integer> max = Stream.of(1, 2, 3).max(Integer::compare);
reduce : stream의 요소들을 결합해서 하나의 결과를 생성.
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
// BinaryOperator란 BiFunction<T,T,T>를 말한다. 즉 두 개의 T파라미터를 받아 T를 반환하는 함수.
Optional<Integer> reduced = Stream.of(1, 2, 3).reduce((a, b) -> a + b);
주의해야 할 것은 여기서 reduce는 overload된 메서드이므로 두 방식을 잘 알아두어야 한다.
첫 번째 방식은 T identity는 default값이므로 이 메서드는 무조건 T를 반환한다
두 번째 방식은 default값이 없기 때문에 Optional의 사용이 필요하다.
count : 스트림의 요소 개수를 반환
long count()
Stream<String> stream = Stream.of("a", "b", "c");
long count = stream.count(); // 3 반환
마찬가지로 주의해야할게, long을 반환하기 때문에 Integer와 같은 primitive type의 wrapper 클래스로 받는 것이 좋다.
forEach : 스트림의 각 요소에 대해 주어진 작업 수행 ➡️ void, 유일하게 반환 값이 없다.
void forEach(Consumer<? super T> action)
// 함수형 인터페이스 Consumer는 한 개의 파라미터를 받아 아무것도 반환하지 않는 인터페이스이다.
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); // "a", "b", "c" 출력
collect : 스트림의 요소를 수집해 리스트, 집합 등의 컬렉션으로 만든다. 두 가지 방법이 있다.
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
// BiConsumer는 위의 consumer와 같다. 반환되는게 없는 것
// Supplier는 파라미터가 없는데 값을 반환하는 인터페이스이다.
// getter나 필드가 없는 생성자를 생각하면 좋다.
List<String> asList = stringStream
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
공식문서에 나와있는 예시로 설명을 해보자면
- Supplier<R> supplier : stream의 값을 받을 새로운 R 타입의 collection을 공급 (new 생성자)
- BiConsumer<R, ? super T> accumulator : stream의 원소 T를 R에 집어넣는 적재 행위 (add method)
- BiConsumer<R, R> combiner : 병렬 스트림에서 사용하기 위한 메서드를 집어넣어야한다.
병렬스트림에서는 스레드별로 각각 새로운 collection를 만들기 때문에,
이렇게 만들어진 각각의 collection을 합치기 위한 방식을 제공해야 한다.
예를 들어 ArrayList라면 addAll을, StringBuilder라면 append를 제공해야 한다.
<R, A> R collect(Collector<? super T, A, R> collector);
// A는 수집 중간에 사용되는 누적기. ArrayList와 같은 리스트가 누적기로 사용된다.
List<String> list = Stream.of("apple", "banana", "cherry")
.collect(Collectors.toList()); // list로 수집
Set<String> set = Stream.of("apple", "banana", "cherry")
.collect(Collectors.toSet()); // 스트림을 집합으로 수집
Map<String, Integer> map = Stream.of("apple", "banana", "cherry")
.collect(Collectors.toMap(
s -> s, // 키: 문자열 자체를 키로 사용
s -> s.length() // 값: 문자열의 길이를 값으로 사용
)); map으로도 가능하다.
참고로, Java 16부터 간편하게 list, set을 만들 수 있게
toList, toSet, toArray의 메서드들은 collect를 쓰지 않고도 바로 사용할 수 있다.
allMatch, anyMatch, noneMatch
스트림의 요소중 위의 제목 순서대로 (전부 만족함/아무거나 만족함/하나도 만족 못함 )일 때 true를 반환한다.
boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)
Stream<String> stream = Stream.of("banana", "cherry");
boolean noneStartWithA = stream.noneMatch(s -> s.startsWith("a")); // true 반환
'연재작 > 프로그래밍 언어' 카테고리의 다른 글
Java - Optional 뽀개기 (2) (0) | 2024.10.11 |
---|---|
Java - Optional 뽀개기 (1) (0) | 2024.10.11 |
Java - Stream 뽀개기 (1) (feat. 함수형 인터페이스) (0) | 2024.10.06 |
monad 패턴, optional, promise, stream (1) | 2024.10.05 |
Java Collection, Map에 내재되어 있는 수학 (0) | 2024.09.27 |