반응형
스트림 API(Stream API)
Stream
- sequence of elements supporting sequential and parallel aggregate operations
- 순차적인 요소(연속된 데이터)의 순차 및 병렬 집계 작업을 처리할 수 있는 연산들의 집합
- 연속된 데이터를 처리하기 위한 연산들의 집합
- 데이터를 담고 있는 저장소(컬렉션)가 아니다.
- Funtional in nature, 스트림이 처리하는 데이터 소스를 변경하지 않는다.
- 스트림으로 처리하는 데이터는 오직 한 번만 처리한다.
- 스트림은 무제한일 수도 있다. (Short Circuit 메소드를 사용해서 제한할 수 있다.)
- 예를 들어, 처음 10개의 데이터만 보겠다라고 제한할 수 있다.
- 중개 오퍼레이션은 근본적으로 lazy 하다.
- 여기서의 lazy는 다 처리하지 않아도 중간에 끝낼 수 있다라는 의미보다는 아래의 의미와 같다.
- 종료 오퍼레이션 (terminal operation)가 오기 전까지는 실행되지 않는다.(스트림 파이프라인) 종료 오퍼레이션이 오기 전까지는 스트림이 정의만 되어 있는 상태이다.
- 손쉽게 병렬 처리할 수 있다.
스트림 파이프라인
- 0 또는 다수의 중개 오퍼레이션 (intermediate operation)과 한개의 종료 오퍼레이션 (terminal operation)으로 구성한다.
- 스트림의 데이터 소스는 오직 터미널 오퍼네이션을 실행할 때에만 처리한다.
중개 오퍼레이션
- Stream을 리턴한다.
- Stateless / Stateful 오퍼레이션으로 더 상세하게 구분할 수도 있다. (대부분은 Stateless지만 distinct나 sorted 처럼 이전 이전 소스 데이터를 참조해야 하는 오퍼레이션은 Stateful 오퍼레이션이다.)
- filter, map, limit, skip, sorted, ...
종료 오퍼레이션
- Stream을 리턴하지 않는다.
- collect, allMatch, count, forEach, min, max, ...
스트림 개념 예제
public class Main {
public static void main(String[] args){
List<String> names = new ArrayList<>();
names.add("history");
names.add("dev");
names.add("apple");
names.add("goo");
/*
종료형 오퍼레이션이 없기 때문에 실행되지 않는다.
스트림의 정의만 있는 상태.
*/
// List<String> collect = names.stream()
// .map((s) -> {
// System.out.println(s); //중개 오퍼레이션(map)은 종료 오퍼레이션이 없으면 출력이 되지 않는다. 중개 오퍼레이션은 근본적으로 lazy 하다는 의미가 바로 이런 특징이다.
// return s.toUpperCase();
// });
System.out.println("===중간 오퍼레이션 수행 시 출력되는 데이터===");
List<String> collect = names.stream()
.map((s) -> {
System.out.println(s); //출력이 된다. 종료 오퍼레이션(collect)이 존재한다.
return s.toUpperCase();
}).collect(Collectors.toList()); //종료 오퍼레이션이 오면 정의된 스트림이 실행된다.
System.out.println("===중간 오퍼레이션 수행 후 적용 된 데이터===");
collect.forEach(System.out::println);
System.out.println("#######################");
/*
스트림이 처리하는 원본 소스는 변경되지 않는다.
*/
names.forEach(System.out::println);
System.out.println("#######################");
/*
스트림을 사용하면 손쉽게 병렬 처리할 수 있다.
*/
//기존 forEach를 사용해서 병렬적으로 처리하기에는 쉽지 않다.
for (String name : names) {
if(name.startsWith("a")) {
System.out.println(name.toUpperCase() + " " + Thread.currentThread().getName());
}
System.out.println(name + " " + Thread.currentThread().getName());
}
System.out.println("#######################");
List<String> collect1 = names.stream().map((s) -> {
System.out.println(s + " " + Thread.currentThread().getName());
return s.toUpperCase();
}).collect(Collectors.toList());
collect1.forEach(System.out::println);
System.out.println("#######################");
//스트림의 parallelStream()을 사용하면 JVM이 알아서 병렬적으로 처리해준다.
//다만 병렬처리를 한다고해서 무조건 빨라지는 것은 아니다.
//병렬처리도 비용이 든다. 쓰레드를 만들고, 병렬적으로 처리한 것을 수집해야하고, 쓰레드 간에 왔다갔다하는 컨텍스트 스위치의 비용이 한 쓰레드에서 처리하는 비용보다 더 클 수도있다.
//다만 데이터가 정말 방대하게 큰 경우, 병렬처리는 도움이 된다. 그러나 대부분의 경우에는 병렬처리가 필요없다. 병렬처리는 데이터 소스와 중간 오퍼레이션에서 처리하는 로직에 따라 많이 달라지기 때문에 특정상황에서 좋다고 말할 수 없다.
//케이스별로 성능을 측정해서 병렬처리를 사용여부를 결정하는 것이 바람직하다.
List<String> collect2 = names.parallelStream().map((s) -> {
System.out.println(s + " " + Thread.currentThread().getName());
return s.toUpperCase();
}).collect(Collectors.toList());
collect2.forEach(System.out::println);
}
}
[참고자료]
반응형
'Java > 기본' 카테고리의 다른 글
자바(Java) Optional API - 1 (Optional 개념 소개) (0) | 2021.06.27 |
---|---|
자바 스트림(Java Stream) API - 2 (스트림 API 활용) (0) | 2021.06.27 |
자바 인터페이스(Java Interface) 변화 - 2 (자바8 인터페이스 구조 변화) (0) | 2021.06.24 |
자바 인터페이스(Java Interface) 변화 - 1 (기본 메소드, 스태틱 메소드) (0) | 2021.06.22 |
자바 람다(Java Lambda) - 4 (메소드 레퍼런스) (0) | 2021.06.20 |