코틀린 & Java

[Android] Java Stream에 대해 알아보자

코딩하는후운 2022. 10. 24. 08:58
반응형

Android Java Stream에 대해 알아보자

Java Stream
스트림이란?

스트림을 이용하면 선언형(즉, 데이터를 처리하는 임시 구현 코드 대신 질의로 표현할 수 있다.)으로 컬렉션
데이터를 처리할 수 있다.

선언형으로 코드를 구현할 수 있다.
즉, 루프와 if조건문 등의 제어 블록을 사용해서 어떻게 동작을 구현할지 지정할 필요없이 선언형 방식으로
동작의 수행을 지정할 수 있다.

filter, sorted, map, collect같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들수 있다.
여러 연산을 파이프라인으로 연결해도 여전히 가독성/명확성이 유지된다.
filter메서드의 결과는 sorted메서드로,
다시 sorted의 결과는 map메서드로,
map메서드의 결과는 collect로 연결이 된다.

결과적으로 데이터 처리과정을 병렬화 하면서 스레드와 락을 걱정할 필요가없다.

선언형: 더 간결하고 가독성이 좋아진다.
조립할수 있음: 유연성이 좋아진다.
병렬화: 성능이 좋아진다.

스트림이란 '데이터 처리 연산을 지원하도록 소스에서 추출된 요소'로 정의할 수 있다.
[연속된 요소]
컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스 제공.
컬렉션은 자료구조이므로 컬렉션에서는 시간과 공간의 복잡성과 관련된 요소 저장 및 접근 연산이 주를 이룬다.
반면 스트림은 filter, sorted, map처럼 표현 계산식이 주를 이룬다.
즉, 컬렉션의 주제는 데이터고 스트림의 주제는 계산이다.

[소스]
리스트로 스트림을 만들면 스트림의 요소는 리스트의 요소와 같은 순서를 유지한다.

[데이터 처리연산]
함수형 프로그래밍 언어에서 일반적으로 지원하는 연산과 데이터베이스와 비슷한 연산을 지원.
스트림 연산은 순차적 또는 병렬로 실행할 수 있다.

[파이프라이닝]
스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 반환.

collect: 스트림을 다른형식으로 변환.

-스트림연산
filter, map, limit는 서로 연결되어 파이프라인을 형성
collect로 파이프라인을 실행한 다음에 닫는다.

연결할 수 있는 스트림 연산을 intermediate operation이라 하며,
스트림을 닫는 연산을 terminal operation이라 한다.

[intermediate operation : 중간연산]
filter나 sorted 같은 중간 연산은 다른 스트림을 반환.
중간연산의 중요한 특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무도 연산을 수행하지 않는다.

[terminal operation : 최종연산]
스트림 파이프라인에서 결과를 도출.
보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환.


스트림 이용하기
1.질의를 수행할(컬렉션 같은) 데이터 소스
2.스트림 파이프라인을 구성할 중간 연산 연결
3.스트림 파이프라인을 실행하고 결과를 만들 최종 연산.

-매핑
특정 객체에서 특정 데이터를 선택하는 작업은 데이터 처리 과정에서 자주 수행되는 연산이다.

[map : 스트림의 각 요소에 함수 적용하기]
스트림은 함수를 인수로 받는 map메서드를 지원.
인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.
이 과정은 기존의 값을 '고친다:modify'라는 개념보다는 '새로운 버전을 만든다'라는 개념에 가까우므로
'변환:transforming'에 가까운 '매핑:mapping'이라는 단어를 사용한다.

[flatMap : 스트림 평면화]
map과 Arrays.stream 활용
String[] arrayOfWords = {"goodbye", "world"}
Stream<String> streamOfworlds = Arrays.stream(arrayOfWords)

파이프라인에 Arrays.stream()메서드를 적용해보자.
words.stream()
.map(word -> word.split("")) //각 단어를 개별 문자열 배열로 반환
.map(Arrays::stream) //각 배열을 별도의 스트림으로 생성
.distinct()
.collect(toList())

flatMap사용

flatMap을 사용하면 다음과 같이 문제를 해결할 수 있다.
List<String> uniqueCharacters =
word.stream()
.map(w->w.split(""))
.flatMap(Arrays::stream) //생성된 스트림을 하나의 스트림으로 평면화
.distinct()
.collect(Collectors.toList())
flatMap은 각 배열을 스트림이 아닌 스트림의 콘텐츠로 매핑한다.
즉, map(Arrays::stream)과 달리 flatMap은 하나의 평면화된 스트림을 반환한다.
flatMap 메서드는 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행.

reduce

리듀싱 연산은 모든 스트림 요소를 처리해서 값으로 도출.
두개의 인수를 갖는다
1.초기값 0
2.두 요소를 조합해서 새로운 값을 만드는 BinaryOperator<T>
(ex. (a,b)->a+b)

reduce 연산 과정에서 스트림이 하나의 값으로 줄어들때까지 람다는 각 요소를 반복해서 조합한다.

iterate

iterate메서드는 초기값과 람다를 인수로 받아서 새로운 값을 끊임없이 생산할 수 있다.
iterate는 요청할때마다 값을 생산할 수 있으며 끝이없으므로 무한 스트림을 만든다.
이러한 스트림을 언바운드 스트림이라고 표현.
이러한 특징이 스트림과 컬렉션의 가장 큰 차이점.

generate

iterate와 비슷하게 generate도 요구할 때 값을 계산하는 무한 스트림을 만들 수 있다.
하지만 iterate와 달리 generate는 생산된 각 값을 연속적으로 계산하지 않는다.
generate는 Supplier<T>를 인수로 받아서 새로운 값을 생성.
limit이 없다면 언바운드 상태가 된다.


반응형