스트림(Stream) 이란
스트림은 데이터 처리연산을 지원하기 위해 소스(source)에서 추출된 연속된 요소(sequence of elements)이다.
스트림의 특징
파이프라이닝(pipelining)
스트림은 연속된 연산처리 작업에 성능을 발휘한다. 원하는 데이터를 추출하고, 정렬하고, 매핑하는 일련의 작업을 한번에 처리하는데 적합하다. 목적에 따라 여러 파이프를 연결할 수 있듯이 스트림을 사용하면 필요에 따라 스트림 연산을 구성할 수 있다.
내부반복(internal iteration)
반복자(Iterator)를 사용하면 외부반복이다. 외부반복은 명시적으로 작업을 지시하는 방법이다. 내부반복에 대해서는 아래에 설명한다.
컬렉션과 스트림의 차이
java 컬렉션에는 List, Set, Map 등이 있다. 컬렉션과 스트림의 차이는 무엇일까. 아래 표로 정리했다.
컬렉션 | 스트림 | |
계산 시점 | 컬렉션에 저장하기 전에 계산 | 요청이 들어오면 계산 |
자료구조 형태 | 유동(=flex) | 고정(=fix) |
요소 추가 및 삭제 | 요소를 추가하거나 삭제 가능 | 요소를 추가하거나 삭제 불가 |
요소 반복 | 외부반복 | 내부반복 |
탐색 횟수 | 반복 가능 | 스트림 한번에 한번만 탐색[소비] 가능 다시 탐색하려면 새로운 스트림을 또 생성해야함 |
계산시점
컬렉션은 자료구조에 들어있는 모든 값을 계산한 상태로 저장한다. 즉 자료구조에 저장된 값은 미리 계산한 값들이다. 다르게 표현하면 컬렉션은 어떤 값인지 정의된 값만 저장한다. 스트림은 요청이 들어오면 그제서야 필요한 값을 확인한다. 보통 게으름[laziness]이라고 표현하는데, 덕분에 사용자가 요청하는 값만 추출해 효율적으로 계산해 낼 수 있다.
컬렉션과 스트림의 계산시점 차이는 보통 DVD 와 인터넷 스트리밍의 차이로 예시를 든다. 요즘에는 DVD 를 쉽게 볼 수 없어서 잘 모르는 사람들을 위해 설명드리자면, CD 안에 영화 등 자료를 저장해 놓은 매체이다. DVD 안에는 전체 영화내용이 DVD에 저장되어 있다. 내가 영화를 처음부터 볼 것인지, 후반 30분부터 볼 것인지 상관없이 일단 전체 영화를 담아놓았다.
인터넷 스트리밍으로 시청할 때는 내가 원하는 시간대 부분을 요청하면, 그때부터 영화가 로딩[계산]되면서 해당 부분부터 시작된다. 전체 영화내용의 대부분은 계산되지 않고, 필요한 때에만 몇 프레임씩 로딩하면서 효율적인 재생이 이루어진다.
자료구조 형태
컬렉션은 유동적인(flex) 구조이다. List를 생각해보자. add(), remove() 메서드로 중간에 요소를 추가 및 삭제할 수 있다. 스트림은 고정된(fix) 구조이다. 요소를 추가하거나 삭제할 수 없다. 파이프라인처럼 요소가 외부로부터 보호되기 때문에 중간에 요소를 건드릴 수 없다.
요소반복
외부반복(exterior iteration)
사용자가 for, while, iterator 등 반복자를 사용해 직접(명시적으로) 요소를 반복하는 방식이다. 예를 들면 흔히 보는 아래와 같은 코드이다.
외부반복 example
List<String> fruitList = Arrays.asList("apple", "banana", "cacao");
//반복1
Iterator<String> iterator = fruitList.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
}
//반복2
for (String fruit : fruitList) {
System.out.println(fruit);
}
//반복3
for (int i = 1; i < fruitList.size(); i++) {
String fruit = fruitList.get(i);
System.out.println(fruit);
}
//반복4
int i=0;
while (i < fruitList.size()) {
String fruit = fruitList.get(i);
System.out.println(fruit);
}
코드를 자세히 보았다면 눈치챘을지 모르지만, 3번째와 4번째 반복코드는 적절하지 않다. 코드로 직접 구현하다 보면 이렇게 사소한 실수를 범할 수 있다.
내부반복(internal iteration)
먼저 아래 예시를 보자. fruitList 에서 똑같이 요소를 가져오는 코드이다.
내부반복 example
List<String> fruitList = Arrays.asList("apple", "banana", "cacao");
//반복1
fruitList.forEach(a -> System.out.println(a));
//반복2
fruitList.forEach(System.out::println);
//반복3
Arrays.asList("apple", "banana", "cacao").forEach(System.out::println);
내부반복과 외부반복이 어떻게 동작하는지 예시로 비교해보자.
외부반복은 아래와 같이 동작한다.
1번째 과일을 가져와! 1번째 과일로 작업을 수행해! 과일 다 가져왔니? 2번째 과일을 가져와! 2번째 과일로 작업을 수행해! 과일 다 가져왔니? 3번째 과일을 가져와! 3번째 과일로 작업을 수행해! 과일 다 가져왔니? ... |
뭔가 불필요한 작업이 개입되어 개운하지 않다. 내부반복을 사용하면 아래와 같이 깔끔하게 동작한다.
과일 리스트에서 과일로 반복작업 수행해! |
위와 같이 내부반복은 작업만 지정해주면 알아서 반복해주는 방식이다. 코드를 작성하며 발생할 실수가 들어갈 여지가 확연히 줄었다. 무엇보다 코드가 줄어서 아름다워 보이기까지 한다.
탐색횟수
컬렉션은 얼마든지 반복해 사용할 수 있다. 아래 fruitList는 언제든 재사용 가능하다.
List<String> fruitList = Arrays.asList("apple", "banana", "cacao");
stream 은 한번 밖에 사용할 수 없다.
Stream<String> fruitStream = Arrays.asList("apple", "banana", "cacao").stream();
fruitStream.forEach(System.out::println);
fruitStream.forEach(System.out::println); //error: stream has already been operated upon or closed
한번 사용한 스트림은 재사용할 수 없다. 위의 예시에서 스트림이 이미 사용되었거나 close 되었다는 오류를 확인할 수 있다.
'Java > 활용' 카테고리의 다른 글
[Java] Stream기본 -스트림으로 List, Set, Map 에 데이터 담기 (0) | 2022.12.20 |
---|---|
[Java] Stream기본 -sort() 메서드로 값 정렬하기 (0) | 2022.12.17 |
[Java] Stream기본 -filter(), map() 메서드로 필요한 값만 가져오기 (0) | 2022.12.14 |