Java

스트림

라우브 2020. 7. 28. 11:11

스트림이란 ?

: 스트림은 '데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소'이다.

  컬렉션과 스트림의 차이를 물어본다면 가장 큰 차이는 '목적'이라고 할 수 있을 것 같다.

  컬렉션이 관련된 요소의 저장 및 접근 연산이 주를 이룬다면, 스트림은 filter, sorted, map 등과 같이 표현 계산식이 주를 이룬다.

  즉, 컬렉션의 주제는 데이터 및 저장이고, 스트림의 주제는 계산이라고 볼 수 있다.

 

컬렉션 vs 스트림 

: 아직 이해가 잘 안가는 사람들을 위해 한 번더 비유를 하자면, DVD에 어떤 영화가 저장되어 있다고 가정하면, DVD는 일종의 컬렉션이라고 볼 수 있다. 반면에 같은 비디오이지만 인터넷 스트리밍으로 비디오를 시청한다면, 이때는 스트림에 가깝다고 볼 수 있다. 스트리밍으로 비디오를 재생할 때는, 전체 비디오를 내려받지 않아도, 일부 프레임있으면 해당 부분을 재생할 수 있다. 즉, 데이터를 언제 계산하느냐가 컬렉션과 스트림의 큰 차이라고 볼 수 있다.

 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다. 반면 스트림은 이론적으로, 요청할 때만 요소를 계산하는 고정된 자료구조이다.

 

외부 반복 vs 내부 반복

컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야한다.  (예를들면 for-each 등을 이용해서).

이를 외부 반복이라 한다. 반면 스트림은 내부 반복을 사용한다.

아래는 컬렉션(외부반복)을 이용하여 음식들로부터 음식 이름만 획득하는 코드이다. 

아래는 내부반복(스트림)을 이용하여 음식들로부터 음식 이름을 추출하는 코드이다.

이해를 돕기위해 시나리오를 통해 설명하자면, 일단 바닥에 어지러진 장난감을 치워야 하는 상황이라고 가정하자.

외부반복을 사용한다면 아래와 같을 것이다.

엄마 : 바닥에 장난감 있니 ?

아이 : 네 있어요 

엄마 : 좋아 그걸 바구니에 담으렴. 또 바닥에 장난감이 있니 ?

아이 : 네 있어요

엄마 : 좋아 그걸 바구니에 담으렴. 또 바닥에 장난감이 있니 ?

아이 : 없어요 

엄마 : 수고했어 

 

하지만 이 상황에서 내부반복을 사용한다면

엄마 : 아이야 바닥에 있는 모든 장난감을 바구니에 담아주렴

아이 : 네

위와같이 간단하게 설명할 수 있을 것 같다. 

 

내부반복이 좋은 이유는 양 손에 장난감을 동시에 들 수 있다는 것이고 (병렬처리 ), 두번째는 

먼저 모든 장난감을 상자 가까이 이동시킨 다음에 장난감을 상자에 넣을 수 있다는 점이다. 스트림(내부반복)을 이용하면

아주 쉽게 병렬처리를 진행할 수 있다.

 

중간연산과 최종연산

스트림은 중간연산과 최종연산으로 구분할 수 있다.

위와 같은 코드에서는, filter, map, limit이 중간연산, collect가 최종연산이다.

스트림 파이프라인은 최종연산이 실행되기 전까지는 아무것도 하지 않는다. 즉 lazy하다.

 

menu에 [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon] 순으로 들어있다고 가정하자.

스트림을 처음 보면,  위의 코드를 실행하면 pork부터 salmon까지 전체를 확인하며 칼로리가 300이상인 음식을 필터링하고, 

그 후에 해당음식의 이름만 뽑아서 다시 리스트를 만들고, 추출된 이름 리스트에서 앞에서 3개를 반환할 것 같지만 그렇지않다.

 

아래와 같이 중간연산에 print를 찍어 확인해보자.

실행 결과

위 결과를  보면 pork 에 대해 filter연산과 map연산이 수행된 후 beef에 대한 연산이 시작되는 것을 알 수 있다. 

즉, 스트림의 모든 요소에 대해 첫번째 중간연산을 실행하고 반환된 리스트에 대해서 그 다음 중간요소를 실행하는 메커니즘이 아니다.

첫번째 요소에 대해 모든 중간연산을 실행하고, 다음 요소에 대해 중간 연산을 실행한다. 이렇게 하면 얻을 수 있는 장점은 효율성이다.

만약 모든 요소에 대해 filter 연산을 수행하고, 그 후 반환된 리스트의 모든 요소에 대해 map 연산을 수행하고, 그 후 반환된 리스트에서 앞에서 3개를 추출했다면, 실행결과에는 훨씬더 많은 로그들이 찍혀있엇을 것이다. 하지만 스트림을 사용하면 pork beef chicken 이 세가지 요소가 추가되자마자 반복을 끝내기 때문에 굉장히 효율적이다.  

 

중간 연산의 대표적인 예로는 filter, map, limit, sorted, distinct가 있고, 

최종 연산의 대표적 예로는 forEach, count, collect가 있다.

 

'Java' 카테고리의 다른 글

equals(), hashCode (), == 연산자  (0) 2021.08.25
동작 파라미터화  (0) 2020.07.27