스트림(Stream)
Java Stream은 자바 8부터 소개된 기능으로, 데이터를 다루는 데 유용한 API이다. Stream은 데이터를 처리하는 연산을 지원하여 코드를 간결하게 작성하고 병렬 처리를 효율적으로 수행할 수 있게 해준다.
스트림은 특이하게 데이터를 읽기만 하고 변경하지는 않으며, 1회용으로 사용된다. (Iterator와 같은 느낌)
Java Stream에서 연산은 크게 중간 연산(intermediate operation)과 최종 연산(terminal operation)으로 나눌 수 있다.
중간 연산은 다른 Stream을 반환하며 연속적으로 체이닝할 수 있으며,
최종 연산은 Stream의 최종 결과를 반환하거나, 외부 자원에 작용하며 Stream의 파이프라인을 종료한다.
1. Stream의 주요 특징
- 선언적 프로그래밍: Stream은 선언적 프로그래밍의 특성을 가지고 있어 코드를 간결하게 만든다. 필요한 데이터 연산을 명시하고, 내부적으로는 어떻게 처리할지는 Stream에게 맡기는 방식.
- 데이터 파이프라인: Stream은 여러 단계의 연산을 조합하여 데이터 파이프라인을 만들 수 있다. 각 단계는 원본 데이터에 대한 연산을 수행하고, 그 결과를 다음 단계로 전달한다.
- 내부 반복: Stream은 내부적으로 요소들을 반복하는 과정을 처리하므로, 사용자가 직접 반복문을 작성하지 않아도 된다.
2. Stream 장단점
- 장점
- 간결한 코드: Stream을 사용하면 반복문을 사용하는 것보다 간결하게 코드를 작성할 수 있습니다.
- 병렬 처리 지원: Stream은 내부적으로 병렬 처리를 지원하므로, 멀티코어 시스템에서 성능을 향상시킬 수 있습니다.
- 단점
- 학습 곡선: 초반에는 익숙하지 않아 학습 곡선이 존재합니다.
- 몇몇 복잡한 상황에서는 부적절: 특정한 복잡한 로직이나 특수한 상황에서는 일반적인 반복문이나 다른 방법이 더 적합할 수 있습니다.
3. Stream 주요 메서드와 사용 방법 (중간연산 / 최종연산)
리스트를 활용하여 스트림 선언
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 스트림 선언 후 중간 연산(filter, map)과 최종 연산(collect)을 사용하여 데이터를 조작
List<Integer> evenNumbers = numbers.stream()
.filter(num -> num % 2 == 0)
.map(num -> num * 2)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 출력: [4, 8]
Stream 주요 메서드
- 중간 연산
- filter(Predicate<T> predicate): 주어진 조건에 맞는 요소만 선택
- map(Function<T, R> mapper): 각 요소를 다른 형태로 변환
- flatMap(Function<T, Stream<R>> mapper): 각 요소를 여러 요소로 확장하고 평탄화
- distinct(): 중복된 요소를 제거
- sorted(): 요소를 기본 정렬 순서로 정렬
- peek(Consumer<T> action): 각 요소에 대해 주어진 동작을 수행하고, 동작 결과를 그대로 반환
- 최종 연산
- forEach(Consumer<T> action): 각 요소에 대해 주어진 동작을 수행
- toArray(): Stream의 요소를 배열로 변환
- reduce(BinaryOperator<T> accumulator): 모든 요소를 하나의 값으로 축소
- collect(Collector<T, A, R> collector): Stream의 요소들을 컬렉션으로 수집
- min(Comparator<T> comparator): Stream의 최솟값을 찾기
- max(Comparator<T> comparator): Stream의 최댓값을 찾기
- count(): Stream의 요소 개수를 반환
- anyMatch(Predicate<T> predicate): 주어진 조건에 맞는 요소가 하나라도 있는지 확인
- allMatch(Predicate<T> predicate): 모든 요소가 주어진 조건을 만족하는지 확인
- noneMatch(Predicate<T> predicate): 모든 요소가 주어진 조건을 만족하지 않는지 확인
- findFirst(): Stream의 첫 번째 요소를 반환
- findAny(): Stream의 아무 요소나 반환
중간연산
이러한 연산들은 스트림을 다른 스트림으로 변환하거나, 스트림의 요소를 필터링하거나 변환하는 등의 작업을 수행한다. 중간 연산은 여러 개를 연결하여 체인으로 사용할 수 있다.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
// Map 생성
List<String> words = Arrays.asList("apple", "banana", "orange", "kiwi");
List<String> result = words.stream()
.filter(s -> s.length() >= 5)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(result);
}
}
출력
[APPLE, BANANA, ORANGE]
최종연산
최종 연산은 스트림의 요소를 소모하면서 최종 결과를 도출한다. 스트림의 처리가 완료되고 더 이상 다른 스트림으로 연결할 수 없게 된다. (종료 후 .count()를 찍어보면 에러가 나오게 된다. [이미 종료되어 닫혔기 때문])
List<String> words = Arrays.asList("apple", "banana", "orange", "kiwi");
words.stream()
.forEach(System.out::println);
출력
apple
banana
orange
kiwi
4. 병렬 스트림(Stream) 사용 방법
다른 방식으로는 병렬 스트림이 있다.
Java Stream은 병렬 처리를 지원하기 위해 parallel() 메서드를 제공한다. 이를 호출하면 병렬 스트림이 생성되어 멀티코어 환경에서 효율적인 작업이 가능합니다. 즉, 멀티스레드로 병렬처리 하기 때문에 처리순서가 보장되지 않지만 빠른 속도로 처리할 수 있다는 장점이 있다.
※ 스레드를 사용하지 않고, parallel()만 추가하고 뒤에는 같은 방식으로 호출하면 된다.
List<String> words = Arrays.asList("apple", "banana", "orange", "kiwi");
// 병렬 스트림 사용
List<String> result = words.parallelStream()
.filter(s -> s.length() >= 5)
.map(String::toUpperCase)
.collect(Collectors.toList());
'Programming > Java' 카테고리의 다른 글
[Java] 스트림(Steam) 사용법 | 민민의 하드디스크 - 티스토리 (0) | 2024.02.16 |
---|---|
[Java] 스레드(Thread)란? (사용 이유, 사용법) | 민민의 하드디스크 - 티스토리 (0) | 2024.02.04 |
[Java - 자료구조] Map(HashMap, Hashtable, TreeMap)이란? (feat. JSON) | 민민의 하드디스크 - 티스토리 (0) | 2024.02.04 |
[Java] 제네릭(Generic)이란? | 민민의 하드디스크 - 티스토리 (2) | 2024.02.02 |
[Java - 자료구조] LinkedList와 ArrayList란? | 민민의 하드디스크 - 티스토리 (0) | 2024.02.02 |