티스토리 뷰
728x90
반응형
스트림이란 무엇인가?
- 스트림은 자바 8 API에 새로 추가된 기능입니다.
- stream은 물류 창고와 비슷합니다. 물류 창고에서 물류를 포장하고(map), 지역별로 정리하고(filter), 배송합니다.
💡 자바 8 이전, 이후 비교
- 다음 예제는 저칼로리의 음식을 반환하고, 칼로리를 기준으로 정렬하는 예제입니다.
- 자바 8이전에는 "낮은_칼로리_음식_목록" 이라는 가비지 변수가 사용하여 데이터를 저장하고 저장한 데이터를 기준으로 정렬을 하고 있습니다. 하지만 자바 8 이후에서는 stream에서 내부 반복을 통해 처리할 수 있습니다.
- 또한 stream에서는 filter, sorted, map, collect와 같은 파이프라인을 연결해 가독성과 명확성을 유지할 수 있습니다.
public class Main {
private static List<Dish> MENU = new ArrayList<>();
static {
MENU = Arrays.asList(
new Dish("pork", false, 800, Type.MEAT),
new Dish("beef", false, 700, Type.MEAT),
new Dish("chicken", false, 400, Type.MEAT),
new Dish("french fries", true, 530, Type.OTHER),
new Dish("rice", true, 350, Type.OTHER),
new Dish("season fruit", true, 120, Type.OTHER),
new Dish("pizza", true, 550, Type.OTHER),
new Dish("prawns", false, 300, Type.FISH),
new Dish("salmon", false, 450, Type.FISH)
);
}
public static void main(String[] args) {
// 자바 8 이전
List<Dish> 낮은_칼로리_음식_목록 = new ArrayList<>();
for (Dish dish : MENU) {
if (dish.getCalorie() < 400) {
낮은_칼로리_음식_목록.add(dish);
}
}
Collections.sort(낮은_칼로리_음식_목록, new Comparator<Dish>() {
@Override
public int compare(Dish o1, Dish o2) {
return Integer.compare(o1.getCalorie(), o2.getCalorie());
}
});
// 자바 8 이후
MENU.stream()
.filter(dish -> dish.getCalorie() < 400)
.sorted(Comparator.comparing(Dish::getCalorie))
.collect(Collectors.toList());
}
}
그래서 스트림이 뭐지
- 스트림이란 "데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소" 로 정의할 수 있다고 합니다. 무슨 말인지 모르겠으니 살펴보겠습니다.
💡 연속된 요소
- 컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스를 제공합니다.
- 컬렉션에서는 List 중에서도 ArrayList와 LinkedList 중 어떤 컬렉션을 사용할건지에 대한 시간과 공간의 복잡성과 관련된 요소의 저장 및 접근 연산이 주를 이룹니다.
- 스트림은 filter, sorted, map 처럼 표현 계산식이 주를 이룹니다.
- 즉 컬렉션의 주제는 데이터이고, 스트림의 주제는 계산식입니다.
💡 소스
- 스트림은 컬렉션, 배열, I/O 등의 자원으로부터 데이터를 제공받아 데이터를 소비합니다.
- 정렬된 컬렉션으로 스트림을 생성하면 정렬이 그대로 유지됩니다.
💡 데이터 처리 연산
- 스트림 연산은 순차적 똔느 병렬적으로 실행할 수 있습니다.
스트림의 특징
💡 파이프 라이닝
- 대부분의 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프 라인을 구성할 수 있도록 스트림 자신을 반환합니다. 그 덕분에 지연, 쇼트서킷과 같은 최적화를 얻을 수 있습니다.
💡 내부 반복
- 컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야 합니다.(for-each) 이를 외부 반복이라 하며, 반면 스트림 라이브러리는 반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장해주는 내부 반복을 사용합니다.
- 스트림 라이브러리의 내부 반복은 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택합니다. 반면 for-each를 이용하는 외부 반복은 병렬성을 스스로 관리해야 합니다.
List<String> dishNameList = new ArrayList<>();
for (Dish dish : MENU) { // 외부 반복을 사용하여 명시적으로 순차 반복
dishNameList.add(dish.getName());
}
List<String> dishNameList = MENU.stream()
.map(Dish::getName)
.collect(Collectors.toList()); // 내부 반복
💡 중간 연산의 lazy
- 여러개의 중간 연산을 통해 메서드 체이닝을 하더라도 그 시점에 코드가 수행되지 않습니다.
- 모든 중간 연산의 샐행 시점은 종료 연산이 호출되는 시점입니다. 그렇기 때문에 중간 연산의 반환타입은 또 다른 스트림입니다.
- 아래 예제 코드는 stringList.stream().filter()를 하더라도 코드는 수행되지 않습니다. 그렇기 때문에 filter 메서드 내부의 출력문은 수행되지 않습니다. 이를 수행시기키 위해서는 종료 연산이 숭행되어야 합니다.
public static void main(String[] args) {
List<String> stringList = List.of("Hi", "Hello", "Java", "Stream");
stringList.stream()
.filter(text -> {
System.out.println(text); // 수행되지 않음
return text.length() > 3;
});
}
💡 원본 데이터를 수정하지 않음
- stream은 처리하는 데이터 소스를 변경하지 않습니다. 즉 내가 어떠한 데이터를 수정하더라도 원본 데이터가 수정되는 것은 아닙니다.
public static void main(String[] args) {
List<String> stringList = List.of("Hi", "Hello", "Java", "Stream");
List<String> upperStringList = stringList.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(stringList); // [Hi, Hello, Java, Stream]
System.out.println(upperStringList); // [HI, HELLO, JAVA, STREAM]
}
💡 스트림으로 처리하는 데이터는 오직 한번만 처리됩니다.
- 반복자와 마찬가지로 스트림도 한번만 탐색할 수 있습니다. 즉 탐색된 스트림의 요소는 소비됩니다.(Consumer)
- 반복자와 마찬가지로 한 번 탐색한 요소를 다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야 합니다.
List<String> stringList = List.of("Hi", "Hello", "Java", "Stream");
Stream<String> upperStringList = stringList.stream()
.map(String::toUpperCase);
upperStringList.forEach(System.out::println);
upperStringList.forEach(System.out::println); // java.lang.IllegalStateException: stream has already been operated upon or closed
스트림 연산
- 연결할 수 있는 스트림 연산을 중간 연산이라 하며, 스트림을 닫는 연산을 최종 연산이라 합니다.
MENU.stream()
.filter(dish -> dish.getCalorie() < 400) // 중간 연산
.map(Dish::getName) // 중간 연산
.limit(3) // 중간 연산
.collect(Collectors.toList()); // 최종 연산
💡 중간 연산
- filter, map과 같은 중간 연산은 다른 스트림을 반환합니다. 따라서 여러 중간 연산자를 연결해서 다양한 질의를 만들 수 있습니다.
- 중간 연산자의 특징은 최종 연산의 수행전까지는 아무런 연산도 수행하지 않습니다. 즉 Lazy합니다. 이는 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종 연산으로 한번에 처리하기 때문입니다.
💡 최종 연산
- 최종 연산은 스트림 파이프라인에서 결과를 도출합니다.
- 보통 최종 연산에 의해 List, Map, Integer, void 등의 스트림 결과가 반환됩니다.
스트림과 컬렉션 비교
- 자바의 기존 컬렉션과 새로운 스트림 모두 연속된 요소의 형식의 값을 저장하는 자료구조의 인터페이스를 제공합니다.
- 연속된이란 순서와 상관없이 아무 값에나 접근하는것이 아니라 순차적으로 값에 접근한다는 것을 의미합니다.
💡 데이터의 계산 시점
- 스트림과 컬렉션의 차이는 데이터를 언제 계산하느냐 입니다.
- 컬렉션은 필요한 모든 값이 계산되어서 자료 구조에 담겨야 합니다. 즉 컬렉션은 컬렉션에 저장하기 전 모든 요소가 계산되어야 합니다.
- 스트림은 데이터를 요청할 때만(소비되는 시점에) 계산합니다. 즉 요청하는 즉시 계산하고 소비합니다.
- 정리하자면 컬렉션은 완성되어야 소비할 수 있고, 스트림은 들어오는 요청을 즉시 계산하고 소비할 수 있습니다.
// 컬렉션이 0~99까지의 자연수를 출력하기 위해서는 자연수를 저장한 후 컬렉션을 순회하며 요소를 출력해야 합니다.
List<Integer> naturalNumbers = new ArrayList<>();
for (int i = 0; i < 100; i++) {
naturalNumbers.add(i);
}
for (int n : naturalNumbers) {
System.out.println(n);
}
// 스트림은 자연수가 하나씩 요청되는 동시에 소비할 수 있습니다.
IntStream.rangeClosed(0, 99)
.forEach(System.out::println);
✔️ 정리
- 스트림은 소스에서 추출된 연속 요소로, 데이터 처리 연산을 지원합니다.
- 스트림은 내부반복을 지원하고, 컬렉션은 외부 반복을 지원합니다.
- 중간 연산은 스트림을 반환하면서 다른 연산과 연결되는 연산입니다. 중간 연산을 이용해 파이프라인을 구성할 수 있지만, 중간 연산만으로는 어떠한 결과를 생성할 수 없습니다.
- 스트림의 요소는 요청할 때 Lazy하게 계산되며 최종 연산에서 한번에 처리됩니다.
✔️ 같이 읽어보면 좋은 글?
- 이펙티브 자바 - 아이템 45 : 스트림은 주의해서 사용하라.
- 이펙티브 자바 - 아이템 46 : 스트림에서는 부작용이 없는 함수를 사용하라.
- 이펙티브 자바 - 아이템 47 : 반환타입으로는 스트림보다 컬렉션이 낫다.
- 이펙티브 자바 - 아이템 48 : 스트림 병렬화는 주의해서 적용하라.
728x90
반응형
'스터디 > 모던 자바 인 액션' 카테고리의 다른 글
모던 자바 인 액션 - 9장 리펙터링, 테스팅, 디버깅 (0) | 2022.09.11 |
---|---|
모던 자바 인 액션 - 8장 컬렉션 API 개선 (0) | 2022.09.10 |
모던 자바 인 액션 - 6장 스트림으로 데이터 수집 (0) | 2022.09.10 |
모던 자바 인 액션 - 3장 람다 표현식 (0) | 2022.09.03 |
모던 자바 인 액션 - 2장 동작 파라미터화 코드 전달하기 (0) | 2022.09.02 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- 레이어드 아키텍처란
- redis sorted set
- spring boot redisson destributed lock
- spring boot 엑셀 다운로드
- spring boot excel download paging
- JDK Dynamic Proxy와 CGLIB의 차이
- 람다 표현식
- transactional outbox pattern
- spring boot redisson sorted set
- pipeline architecture
- 자바 백엔드 개발자 추천 도서
- redis 대기열 구현
- spring boot excel download oom
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- polling publisher spring boot
- transactional outbox pattern spring boot
- spring boot redisson 분산락 구현
- redis sorted set으로 대기열 구현
- service based architecture
- java userThread와 DaemonThread
- spring boot redis 대기열 구현
- java ThreadLocal
- space based architecture
- 서비스 기반 아키텍처
- spring boot poi excel download
- 공간 기반 아키텍처
- microkernel architecture
- @ControllerAdvice
- pipe and filter architecture
- 트랜잭셔널 아웃박스 패턴 스프링부트
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 28 | 29 | 30 |
글 보관함