들어가는 글

스트림의 내용을 한 페이지에 다 담는다면 너무 방대 할 것이다.
천천히 나도 익혀가면서 스트림에 대해서 정리할 예정이다.
현재는 간단한 스트림의 개념에 대해서 다룰 예정이다.

 

 

 

스트림(Stream) 이란?

스트림의 사전적 의미는 '흐르다' 또는 '개울'입니다.

프로그래밍에서의 스트림도 사전적 의미와 크게 다르지 않습니다.

다만, 여기서는 물이 흐르는 것은 아니고 '데이터의 흐름'을 말합니다.

자바에서 Stream은 컬렉션 등의 요소를 하나씩 참조해 함수형 인터페이스(람다식)를

통해 반복적인 작업의 처리를 가능하게 해준다.

Stream이 반복적인 일의 처리가 가능하므로,

반복문(for-loop 등)을 대신해 Stream을 사용하는 경우가 많다.

📍 책에서는...  

모던 자바 인 액션 - 스트림은 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 값 요소  
이것이 자바다 - 스트림은 컬렉션의 요소를 하나씩 참조해 람다식으로 처리할 수 있는 반복자  

Stream을 공부하다보면 아래와 같은 이미지를 볼 수 있습니다.
Stream ~ collect 까지의 가는 방식이 파이프길이 있는 것과 닮았다고해서 파이프라이닝 이라고 부르기도 합니다.

 

 

 

 

 

 

 

 

 

Stream의 특징

람다식으로 요소 처리 코드를 제공하는 점과,

내부 반복자를 사용하므로 병렬처리가 쉽다는 점,

중간처리와 최종 처리 작업을 수행하는 점에서 많은 차이를 가지고 있다.

  📍외부반복자(external iterator)란?
    개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴을 말함. 
    반면에 내부반복자(internal iterator)는 컬렉션 내부에서 요소들을 반복시키고,
    개발자는 요소당 처리해야할 코드만 제공하는 코드 패턴을 말함. 

  ex) 대표적으로 for문은 외부반복자, Stream은 내부반복자이다. 
      for문은 개발자가 직접 코드로 반복을 수행한다.

    📍중간처리와 최종 처리 작업을 수행?
    스트림은 중간처리와 최종처리를 할 수 있다. 
    중간처리에서는 매핑, 필터링, 정렬을 수행하고
    최종처리에서는 반복, 카운팅, 평균, 총합등 집계 처리를 수행한다. 

  ex) 파이프라이닝에서 중간처리는 filter ~ map 이고 최종 처리는 collect 입니다.

 

 

 

 

 

 

 

 

 

스트림 vs for문

표현방식

for문은 코드블럭으로 표현하고 Stream 파이프라인은 함수 객체로 표현합니다.

 스트림의 람다식 표현은 몇가지 제약이 있는데 
 대표적으로 람다이기 때문에 지역 변수를 수할 수 없습니다.

 

 

 

 

내부반복과 외부반복

외부반복은 how중심 그리고 내부반복은 what중심의 코드라고 볼 수 있습니다.

 

 

 

 

가독성

사실 가독성은 for문과 Stream중 개발자가 더 선호하는 것에 따라 다르기에 왈가왈부 할 수 없다.

 

 

 

 

디버깅

  • for문
for문은 반복하는 도중 디버깅이 가능하며 에러가 콘솔에 몇 라인에 에러가 발생했는지 출력 됩니다.
  • Stream
Stream은 반복도중 에러가 발생했다면 어떻게 발생했는지 디버깅하기가 어려우며 

콘솔에 몇 라인에 에러가 발생했는지도 정황하게 출력되지 않습니다.

 

 

 

병렬처리

  • for문
병렬로 처리 하고싶다면 대표적으로 Runnable을 사용해 

로직을 구현하는 등 개발자가 신경쓸게 많아진다.
  • Stream
Stream은 병렬Stream을 이용하면 자동으로 내부적 병렬처리가 가능하다.

 

 

 

성능

📍 실험 1
벤치마크를 위해 총 500000개의 primitive type int를 저장하는 배열을 하나 만들고,
배열에서 가장 큰 원소를 찾는 함수를 각각 for-loop와 순차 스트림으로 테스트

// for-loop
int[] a = ints;
int e = ints.length;
int m = Integer.MIN_VALUE;
for (int i = 0; i < e; i++) {
    if (a[i] > m) {
        m = a[i];
    }
}
// sequential stream
int m = Arrays.stream(ints).reduce(Integer.MIN_VALUE, Math::max);
⭐️ 결과
   for    - 0.36ms  
   Stream - 5.35ms

for-loop은 0.36ms가 소요된 반면, 순차 스트림은 5.35ms가 소요되었다.
약 15배 정도 빠른 것 이다. 스트림이 유독 느린 이유는 무엇일까?
Langer씨는 JIT Compiler가 for-loop을 워낙 40년 이상 다뤄왔다 보니까
for-loop에 대한 internal optimization이 이미 잘 되어 있다고 말한다.
하지만 스트림은 2015년 이후에 도입되었으므로 아직 컴파일러가 최적화를 제대로 하지 못했다는 것이다.

📍 실험 2 ArrayList를 하나 만들고, 500000개의 Integer 타입을 저장하도록 만들자. 이후,
Integer 중 가장 큰 원소를 리턴하도록 하자. 결과는 어땠을까?

⭐️ 결과
   for    - 6.55ms  
   Stream - 8.33ms

for-loop은 6.55ms가 걸린 반면, stream은 8.33ms가 소요 되었다.
for-loop이 약 1.27배 정도밖에 빠르지 않은 것이다. 그 이유는 무엇일까?
ArrayList를 순회하는 비용 자체가 워낙 크기 때문에, for-loop와 stream 간의 성능 차이를 압도해버리는 것이다.

 

 

 

 

마치는 글

회사에 담당개발자가 for문보다 Stream을 쓰도록 강요했는데 처음엔 익숙치 않아서 힘들었지만
익숙해지니 Stream 만큼 편한게 없다고 생각도 들고 코드가 간결해지는게 눈에 보여 신기했다.
그리고 무엇보다 멋난다. 

 

 

 

 

 

참고한 영상 및 글

10분 테코톡 🍡 춘식의 Stream
10분 테코톡 크리스, 로마의 stream vs for
Stream이란? - 기본 개념과 특징 (JAVA)

복사했습니다!