Stream
스트림은 데이터 처리 연산을 위해 자바에서 제공하는 인터페이스이다.
내부적으로 어떻게 작동하는지 궁금해서 살펴보았는데,
spliterator를 인자로 받아, 다음과 같이 RefrenecePipleLine.HEAD를 반환한다.
HEAD는 Spliterator<?> source부터 상위 클래스로 인자를 전달한다.
인자 중 Spliterator는 Collection이라면 Collection Source에서 요소를 탐색하고 분할하여 반환하기 위해 사용하는 Java8 제공 인터페이스이다.
상위 클래스 ReferencePipleline은 그 상위 클래스 AbstractPipleline에 인자를 전달하고,
다음과 같이 전달받은 source를 sourceSupplier에 전달하고
함수형 인터페이스인 Supplier는 전달받은 인자를 반환하는 것이다.
즉 Stream이라는 것은 '데이터 처리 연산을 위해 Collection의 Source로부터 값들을 전달받아 function interface인 Supplier를 통해 값을 반환하며 ReferencePipeline을 구성하고, 반환된 값들에 처리연산 함수인 Map, Filter등을 사용하여 데이터 처리를 수행하는 클래스' 라고 볼수 있다.
그렇다면?
Stream vs List
1. 데이터 계산 시점
이처럼 스트림과 컬렉션의 차이는 데이터를 언제 계산하는지 이다.
- 컬렉션 : 모든 요소는 컬렉션에 추가하기전에 계산되어야 한다.
- 스트림 : 요청할때만 요소를 계산하는 고정된 자료구조
사용자가 요청하는 값만 추출할수 있는 특성때문에 스트림은 컬렉션보다 프로그래밍에 장점이 있다.
2. 반복의 일회성
컬렉션과 스트림은 반복처리를 할때도 차이가 있습니다. 컬렉션의 경우 같은 소스에 대하여 여러번
반복 처리를 할수 있지만 스트림은 단 한번만 반복문을 처리할수 있다.
스트림에서는 소비(Consumer) 개념을 쓰기 때문에 한번 소비한 요소에 대해서 접근할수 없기 때문이다..
Stream<Food> s = foodList.stream();
s.forEach(System.out::println); // 정상
s.forEach(System.out::println); // IllegalStateException 발생
만약 위 코드를 실행한다면 stream has already been operated upon or closed 라는 에러와 함께
프로그램이 중단될것이다.
3. 외부반복, 내부반복
컬렉션의 경우 foreach 문법을 사용하여 사용자가 반복문을 직접 명시해야 하는데 이를 외부반복 이라 하고
스트림은 라이브러리를 사용하는 내부반복 개념이다.
[컬렉션]
List<String> foodNameList = new ArrayList<>();
for(Food food : foodList){
foodNameList.add(food.getName());
}
[스트림]
List<String> foodNameList = foodList.stream()
.map(Food::getName)
.collect(Collectors.toList());
컬렉션과 다르게 스트림은 별도의 반복자 없이도 반복문을 처리할수 있다. 스트림이 사용하는 내부반복의
장점은 작업을 병렬로 처리할수 있고 더 최적화된 다양한 순서로 처리할수 있다는 점이다.
4. 스트림 연산
java.util.stream.Stream 에는 스트림 API에서 제공하는 여러가지 연산이 정이 되있는데 스트림 연산들은
크게 중간연산, 최종연산으로 구분할수 있습니다.
- 중간연산 : 파이프라인으로 연결할수 있는 연산들
- 최종연산 : 파이프라인을 실행한다음 닫는 연산
List<String> highCaloriesFoodName = foodList.stream()
.filter(food -> food.getCalories() > 400) // 중간연산
.map(Food::getName) // 중간연산
.limit(3) // 중간연산
.collect(Collectors.toList()); // 최종연산
위 예제코드에서 보면 filter, map, limit은 중간연산이고 collect는 최종연산 입니다.
[중간연산]
filter나 map 같은 중간연산은 다른 스트림을 반환하기 때문에 여러개의 중간연산을 연결하여 질의를 만들수
있습니다. 중요한 특징은 최종연산을 실행하기 전까지는 아무 연산도 수행하지 않는다는것입니다.
스트림 파이프라인에서 연산이 어떻게 진행되는지 확인해보기 위해 연산에 출력문을 넣어서 확인 해보겠습니다.
List<String> highCaloriesFoodName = foodList.stream()
.filter(food -> {
System.out.println("filter : " + food.getName());
return food.getCalories() > 400;
})
.map(food -> {
System.out.println("map : " + food.getName());
return food.getName();
})
.limit(3)
.collect(Collectors.toList());
System.out.println(highCaloriesFoodName);
[출력]
filter : FlatBread
map : FlatBread
filter : OnionSoup
filter : LobsterRisotto
map : LobsterRisotto
filter : CaesarSalad
filter : BeefWellington
map : BeefWellington
[FlatBread, LobsterRisotto, BeefWellington]
OnionSoup, CaesarSalad는 filter에서 필터링 되었기 때문에 map에서는 찍히지 않고 나머지 음식들이
최종연산되어 출력되는것을 확인할수 있습니다.
[최종연산]
최종연산은 파이프라인 연산의 결과를 출력합니다. 예제에서는 List 형태로만 결과를 받았지만 이 외에도
Integer, void 등 다양한 형태로 출력할수 있습니다.
[요약]
스트림을 사용하는 단계는 다음과 같이 3단계에 걸쳐서 진행됩니다.
- 질의를 수행할 데이터소스 (ex 컬렉션)
- 스트림 파이프라인을 구성할 중간 연산
- 스트림 연산을 실행하고 결과로 출력할 최종연산
나중에 보면 더 좋은 내용 : https://cyk0825.tistory.com/73
Java - Stream
Java Stream Stream은 어떤 연속된 데이터를 처리하는데 필요한 오퍼레이션의 모음이라고 생각하면 좋다. 그 자체가 데이터가 아니고 컬렉션 안에 있는 데이터들을 소스로 사용해서 어떤 처리를 하
cyk0825.tistory.com
Java Stream API는 왜 for-loop보다 느릴까?
The Korean Commentary on ‘The Performance Model of Streams in Java 8" by Angelika Langer
jypthemiracle.medium.com
https://velog.io/@cham/JAVA-Spliterator
[JAVA] Spliterator
Java 8은 병렬 작업에 특화되어 있는 Spliterator라는 새로운 인터페이스를 제공한다.SpliteratorT는 Spliterator를 탐색하는 요소의 형식을 가리킨다.tryAdvance는 Spliterator 요소를 하나씩 소비하면서 탐색해
velog.io
'프로그래밍 언어 > Java' 카테고리의 다른 글
동기화 처리 Synchronized (0) | 2022.06.12 |
---|---|
Stream 확실히 알기 (0) | 2022.06.12 |
제네릭(Generic) (0) | 2022.06.08 |
Java Tomcat OutOfMemoryError : PermGen space (0) | 2022.05.02 |
JVM 메모리 구조 (0) | 2022.05.02 |