스트림(Stream)
자바(Java) 세계에서 언제부터인지 스트림(Stream) 이라는 단어를 목격하게 되었다. 내 기억으로는 Java 8 에서부터 시작된 것 같은데 난데없는 이 단어가 왜 그렇게 핵심이 되었는지가 의문이였다. 도대체 왜 스트림(Stream) 이냐 하는 질문에 대한 대답을 듣기도 어려웠던 시절이기도 하다. 그져 사용하는 방법을 익히는데에 몰두하는 모습만 목격됐을 뿐이다.
java.util.stream
스트림(Stream) 에 대한 정의는 다양하다.
데이터 소스(Array, List) 로부터 흐름을 가지는 데이터의 집합체이며 통합연산을(bulk processing) 통해 데이터를 변형시키고 최종적으로 소비자가 그 데이터를 소비하도록 한다.
스트림을 다루게 되면 항상 다음과 같은 데이터 소스들을 만나게 된다. 모두 데이터의 집합체들이다.
- Array
- List
하필 왜 데이터 집합체들일까
컴퓨터 알고리즘 필요성과 유사한 스트림(Stream)
난데 없이 컴퓨터 알고리즘을 꺼내온 이유가 있다. 컴퓨터 알고리즘을 공부할때에 가장 먼저 만나는 것이 정렬(sort)문제이다. 그런데, 이런 질문을 하게된다.
왜 하필 정렬부터 인가?
이에 대한 대답은 간단다.
Compute 연산과 Memory 공간을 절약하기 위해서..
컴퓨터가 중복된 데이터를 어떻게 찾아낼까? 정렬을하면 쉽게 해결된다. 정렬된 데이터가 아니라면 모든 데이터를 비교해야 하지만 정렬할 경우에 같은 위상을 같은 데이터 값이 나오게 되는데 이를 하나만 남기고 지우면 간단해 진다.
이렇게 함으로써 Memory 공간도 절약하게 되고 이렇게 중복되지 않은 데이터를 가지고 Compute 연산을 할 경우에 당연히 그에 들어가는 비용도 줄게 된다.
자바에서 스트림도 이와 유사하다.
자바에서 데이터를 다루는 방법은 다양한다. 이는 데이터 소스를 통해서 다루어지는데, 이 데이터 소스를 간단하게 타입(Type) 이라고 생각해보자. 정수형, 문자열 등은 가장 단순한 타입이다.
1 2 |
int a = 10; String b = "systemv"; |
이런 타입들은 단 하나의 데이터만 저장하고 있을 뿐 “데이터들” 을 가지고 있지 않다. Compute 연산 알고리즘에서는 여러 데이터들의 집합을 다룬다. 컴퓨터가 가지고 있는 데이터들이란 집합을 이야기 한다. 따라서 데이터 소스라고하면 “데이터들” 을 지칭하며 자바에서 이런형태의 데이터 타입은 Array, List 가 대표적이다.
1 2 3 4 5 |
private static Employee[] arrayOfEmps = { new Employee(1, "Amazon", 10.0), new Employee(2, "MSFT", 20.5), new Employee(3, "Samsung", 30.0) }; |
그럼 이런 생각을 하게된다. 데이터 집합체들을 어떻게 하면 빠르게 중복을 제거하고 연산을 하게 만들 것인가? 과거에 For loop 문과 같은 것을 이용해서 조건식을 붙이면서 사용을 할 수도 있다.
람다(Lambda)
연속된 데이터들을 다루기만 할 거라면 단순하게 For loop 문을 이용하면 된다. 만일 이런 생각을 하게 된다.
연속된 데이터를 처리할때에 병렬을 이용해서 처리보자.
For loop 문에서 병렬처리는 쉬운게 아니다. Thread 를 이용할 수도 있지만 이건 동시성 프로그래밍이지 병렬은 아니다.
이를 위해서 자바 8 에서는 람다(Lambda) 를 도입했다. 이것에 대한 정의를 보면 함수형 프로그래밍(Funtional Programming) 이라는 말을 자주 접하게 되는데 병렬연산을 가능하게 하는 부분이다.
자바 8 스트림은 이 람다를 기반으로 한다. 결국에 스트림은 벌크 프로세싱(Bulk Processing) 을 람다를 사용해 구현하여 빠른 고속 데이터 처리가 가능하다.
스트림 – 흐른다.
스트림의 중요한 특징은 흐름이다. 프로그래밍에서 데이터를 다룰때 흐름 없이 다루는 경우도 많다. 앞에서 컴퓨터가 다루는 데이터는 “데이터들” 이라고 했는데, 이것들을 흐름을 가지고 연산을 수행하는게 스트림이다.
“흐른다” 라는 말을 수도관을 떠올리게 한다. 왼쪽에 물을 흘려보내면 오른쪽으로 물이 나온다. 데이터를 왼쪽에서 흘려보내면 오른쪽으로 물이 나온다. 만일 이 물이 설탕물로 만들고 싶다면 중간에 설탕을 뿌리면된다. 이물질을 제거하고 싶다면 이물질 제거기를 설치하면 된다.
이렇게 보면 누군가 데이터를 흘려보내는 놈이 필요하고 데이터를 받아 마시는 놈이 필요하게 된다. 이것을 Producer 와 Comsumer 관계라고 부른다.
리액티브 와 무슨 관계?
자바 스트림과 Reactive 관계보다 차이가 존재한다.
스트림(Stream) 은 데이터를 생산하면 즉각 소비가 발생한다. 하지만 리액티브 는 그렇지 않다. 리액티브 은 시간이 지남에 따라서 생산과 소비가 발생한다. 생산과 소비가 즉각적이지 않다.
이말을 잘 생각해 볼 필요가 있다. 스트림은 데이터를 다루는 영역에서 매우 유용할 수 있다. 프로그래밍 연산을 할 경우에 적합하게 사용되어질 수 있다. 하지만 Reactive 는 프로그래밍 연산보다 네트워크를 통한 데이터 요청과 리턴에 접합한 모델이라고 할 수 있다.
차이는 또 있다. 리액티브 에서 생산자는 반드시 흐름 데이터만 만들지 않는다. 대표적으로 웹에서 클릭(Click) 조차도 리액티브 에서 생산자가 될 수 있다. 그래서 연속된 데이터 흐름이 없다보니 뭔가 생산하는 개념이 아닌것이여서 생산자(Producer) 라는 말을 쓰지 않는다.
“즉각적으로 소비가 발생하지 않는다” 라는 말도 중요하다. 비동기적으로 데이터 리턴이 발생한다는 것을 의미 한다. 하지만 리턴 값을 받기 위한 준비는 항상하고 있다는것도 중요하다.
리액티브 는 네트워크를 통한 데이터 요청, 리턴 모델에 적합하다. 리액티브 요청한 것에 대한 데이터들을 다룰때에는 스트림을 이용할 수도 있다.