Java8 In Action 을 읽고 정리한 내용이다. 스트림의 개념을 설명하고 스트림과 컬렉션을 비교 설명한다.
스트림이란 무엇인가?
스트림은 자바 API에 새로 추가된 기능으로, 스트림을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있고, 멀티 스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.
즉, 데이터 처리연산을 지원하도록 소스에서 추출된 연속된 요소 이다.
Dish
1 |
|
Java7 코드
1 | public class StreamBasic { |
결과 화면1
2season fruit
rice
Java8 코드
1 | public class StreamBasic { |
결과 화면1
2season fruit
rice
Java8 코드 병렬 처리
stream()
을 parallelStream()
으로 바꾸면 이 코드를 멀티코어 아키텍처에서 병렬로 실행할 수 있다. 결과적으로 우리는 데이터 처리 과정을 병렬화하면서 스레드와 락을 걱정할 필요가 없다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class StreamBasic {
public static void main(String[] args) {
getLowCaloricDishesNamesInJava8Parellel(Dish.menu).forEach(x -> System.out.println(x));
}
public static List<String> getLowCaloricDishesNamesInJava8Parellel(List<Dish> dishes) {
List<String> collect = dishes.parallelStream() // 병렬처리
.filter(d -> d.getCalories() < 400) // 400 칼로리 이하의 요리 선택
.sorted(comparing(dish -> dish.getCalories())) // 칼로리로 요리 정렬
.map(dish -> dish.getName()) // 요리명 추출
.collect(toList()); // 모든 요리명을 리스트에 저장
return collect;
}
}
결과 화면1
2season fruit
rice
스트림 연산을 연결해서 스트림 파이프라인 형성
graph LR menu --> filter filter --> sorted sorted --> map map --> collect
스트림과 컬렉션
컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조이고, 스트림은 요청할 때만 요소를 계산하는 고정된 자료구조이다.
딱 한 번만 탐색할 수 있다!
스트림은 단 한 번만 소비 할 수 있다.1
2
3
4
5
6
7
8
9
10public class StreamVsCollection {
public static void main(String...args){
List<String> names = Arrays.asList("Java8", "Lambdas", "In", "Action");
Stream<String> s = names.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); // java.lang.IllegalStateException 스트림이 이미 소비되었거나 닫힘
}
}
결과 화면1
2
3
4
5
6
7
8Java8
Lambdas
In
Action
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.base/java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
at me.action.chapter3.StreamVsCollection.main(StreamVsCollection.java:23)
스트림은 한번 소비 하게 되면
java.lang.IllegalStateException
를 발생 시킨다. 여기서 컬렉션과 스트림의 또 다른 차이점은 데이터 반복 처리 방법이다.
외부 반복과 내부 반복
컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야 되는 것을 외부 반복(external iteration)
이라고 하고, 스트림 라이브러리는 내부 반복(internal iteration)
을 사용한다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class StreamVsCollection {
public static void main(String...args){
List<String> names = Arrays.asList("Java8", "Lambdas", "In", "Action");
// 외부 반복
List<String> nameList = new ArrayList<>();
for (String name : names) {
nameList.add(name);
}
System.out.println("nameList = [" + nameList + "]");
List<String> nameList2 = new ArrayList<>();
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
nameList2.add(name);
}
System.out.println("nameList2 = [" + nameList2 + "]");
// 내부 반복
List<String> externalNames = names.stream()
.map(s1 -> names.get(0))
.collect(toList());
externalNames.forEach(x -> System.out.println("externalNames = [" + x + "]"));
}
}
결과 화면1
2
3
4
5
6nameList = [[Java8, Lambdas, In, Action]]
nameList2 = [[Java8, Lambdas, In, Action]]
externalNames = [Java8]
externalNames = [Java8]
externalNames = [Java8]
externalNames = [Java8]
여기서 가장 큰 차이점은 스트림라이브러리의 내부 반복은 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택한다. 반면
for-each
를 이용하는 외부 반복에서는 병렬성을 스스로 관리 해야하는 단점이 있다.
스트림 연산
filter와 map처럼 스트림을 반환하면서 다른 연산과 연결될 수 있는 연산을 중간 연산
이라고 한다. 중간 연산을 이용해서 파이프라인을 구성할 수 있지만 중간 연산으로 어떤 결과도 생성할 수 없다. forEach나 count처럼 스트림 파이프라인을 처리해서 스트임이 아닌 결과를 반환하는 연산을 최종 연산
이라고 한다.
graph LR menu --> filter filter --> map map --> limit limit --> collect
- 질의를 수행할(컬렉션 같은)
데이터 소스
- 스트림 파이프라인을 구성할
중간 연산(filter, map, limit)
연결 - 스트림 파이프라인을 실행하고 결과를 만들
최종 연산(collect)
중간 연산
연산 | 형식 | 반환 형식 | 연산의 인수 | 함수 디스크립터 |
---|---|---|---|---|
filter | 중간연산 | Stream |
Predicate |
T -> boolean |
map | 중간연산 | Stream |
Function<T, R> | T -> R |
limit | 중간연산 | Stream |
||
sorted | 중간연산 | Stream |
Comparator |
(T, T) -> int |
distinct | 중간연산 | Stream |
최종 연산
연산 | 형식 | 목적 |
---|---|---|
forEach | 최종연산 | 스트림의 각 요소를 소비하면서 람다를 적용한다. void를 반환한다. |
count | 최종연산 | 스트림의 요소 개수를 반환한다. long을 반환한다. |
collection | 최종연산 | 스트임을 리듀스해서 리스트, 맵, 정수 형식의 컬렉션을 만든다. |