티스토리 뷰
스트림이란 무엇인가?
<모던자바인액션>은 스트림을 '데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소(sequence of elements)'로 정의한다.
1) 연속된 요소: 컬렉션처럼 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스를 제공하고, 이 연속된 요소에 대한 계산식을 제공함
2) 소스: 컬렉션, 배열, I/O 자원 등 데이터 제공 소스로부터 데이터를 소비한다.
3) 데이터 처리 연산: 함수형 프로그래밍 언어에서 일반적으로 지원하는 연산과 데이터베이스와 비슷한 연산을 지원한다.
보다 더 쉬운 설명으로는 '스트림은 데이터 컬렉션 반복을 멋지게 처리하는 기능이다'라고 정의한다.
스트림의 장점
1) 선언형: 간결하고 가독성이 좋아짐
2) 조립할 수 있음: 유연성이 좋아짐
3) 병렬화: 성능이 좋아짐
책을 읽으면서 내가 이해한 스트림은 '자바 안에서 이루어지는 데이터 처리를 선언형으로 유연하게 할 수 있게 끔 해주는 기능' 이다.
유연하게 라는 것은 두 가지로 설명할 수 있는데 바로 조립성과 병렬화이다.
스트림의 조립성이란?
스트림은 filter, sorted, map, collect 등 여러 고수준 빌딩블록(high-level building block)을 연결해서 하나의 파이프라인으로 만들 수 있다. 즉 여러 스트림을 조립해서 하나의 일련의 스트림을 만들 수 있다는 것이다.
List<String> threeHighCaloricDishNames =
menu.stream()
.filter(dish -> dish.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(toList());
System.out.println(threeHighCaloricNames);
이 예제 코드를 보면 menu 라는 리스트에 담긴 요리들 중 칼로리가 300보다 높은 요리 3개를 선택해서 이름만 가져온다. 위의 코드를 도식화 하면 밑의 그림과 같다.
앞서 언급한 고수준 빌딩블록이라는 용어를 그림을 보면 이해할 수 있다. filter, map, limit, collect가 각 블록이 된다는 것이다. 선언형 프로그래밍의 핵심은 동작방식 구체적 명세를 추상화하는 것에 있다. 즉, 우리가 filter를 호출할 때 이 스트림은 추상화 되어있기 때문에 filter가 어떻게 내부적으로 작동하는지 하나도 신경 안쓰고 그저 가져다가 쓸 수 있다는 것이다. 추상화라는 생각하면 왜 filter를 고수준 빌딩블록 이라고 하는지 이해가 된다.
두 가지 중요한 특징:
1) 파이프라이닝: 대부분의 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 반환한다.
2) 내부반복: 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부반복을 지원한다.
내부반복 vs 외부반복
컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야 한다. for-each나 Iterator를 이용해서 반복할 수 있으며 이를 외부 반복(external iteration)이라고 한다. 반면 스트림 라이브러리는 반복을 알아서 처리하고 결과 스트림 값을 저장해주는 내부 반복(internal iteration)을 사용한다.
내부 반복의 장점은 병렬성을 쉽게 얻을 수 있다는 점이며, 내부적으로 더 최적화된 방법으로 처리될 수 있기 때문이다.
중간연산, 최종연산
스트림은 연결할 수 있는 스트림 연산인 중간 연산(intermediate operation)과 스트림을 닫는 연산인 최종 연산(terminal operation)으로 구성된다.
중간 연산은 다른 스트림을 반환한다. 따라서 여러 중간 연산을 연결해 질의를 만들 수 있다. 중간 연산의 가장 중요한 특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무도 연산을 수행하지 않는다는 것, 즉 게으르다(lazy)는 것이다. 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종 연산으로 한 번에 처리하기 때문이다.
List<String> names = menu.stream() // 스트림 open
.filter(dish -> dish.getCalories > 300) // 중간 연산 시작
.map(Dish::getname)
.limit(3) // 중간 연산 끝, short-circuit
.collect(toList()); // 종단 연산
스트림의 게으른 특성 덕분에 얻을 수 있는 최적화 효과가 있다.
첫 번째는 쇼트 서킷이다. 모든 연산을 다 해보기 전에 조건을 만족하면 추가적인 불필요한 연산은 하지 않는다. 위의 예시에서는 limit 연산이 쇼트 서킷 연산에 해당된다. 3개의 결과를 얻은 후 앞선 filter와 map연산은 더 이상 수행할 필요가 없어 빠르게 최종 연산을 수행한다.
두 번째는 루프 퓨전이다. 위의 예시 코드에서 filter와 map 연산에 값을 print 하는 과정을 추가한다면 filter와 map이 다른 연산이지만 한 과정으로 병합되어 처리됨을 확인할 수 있다. 루프 퓨전은 이렇게 둘 이상의 연산이 합쳐 하나의 연산으로 처리됨을 말한다.
최종 연산은 스트림 파이프라인에서 결과를 도출한다. 스트림 외의 결과를 반환하는 연산을 말한다.
'Java > 기본기' 카테고리의 다른 글
[JAVA8] 6. Optional (0) | 2022.04.17 |
---|---|
[JAVA 8] 1. 동작 파라미터화 (0) | 2022.02.19 |
[JAVA] Java는 정말 WORA할까? (0) | 2021.08.11 |
[Java 기본] I-1. JVM이란 무엇인가 (0) | 2021.07.13 |
[JAVA] Wrapper 클래스는 왜 있는 걸까? (0) | 2021.03.05 |
- Total
- Today
- Yesterday
- KAKAO 2021
- PatternSyntaxException
- Spring
- decorator
- Java #GC #가비지콜렉터 #Garbage Collector
- Java #JIT #JVM
- behavior parameterization
- 프로그래밍 모델
- WORA
- zipkin
- 카카오
- 코테
- WORE
- 모던 자바 인 액션
- nginx 내부
- Java
- 신규 아이디 추천
- 2021
- 2020 KAKAO
- 카카오코테
- Kakao Blind
- spring cloud sleuth
- IOC
- 카카오 코테
- 2019 Kakao Blind
- 카카오 인턴
- 스프링
- 디자인패턴
- jvm
- okhttp3
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |