스트림
업데이트:
스트림
- 자바 8 API에 새로 추가된 기능으로 이용하면 선언형(데이터를 처리하는 임시 구현 코드 대신 질의로 표현할 수 있다)으로 컬렉션 데이터를 처리할 수 있다.
- 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.
// List<String> lowCaloricDishesname = menu.stream() // 스트림을 사용한코드 List<String> lowCaloricDishesname = menu.parallelStream() // 병렬로 실행 .filter(d -> d.getCalories() < 400) // 400칼로리 이하의요리 선택 .sorted(comparing(Dish::getCalories)) //칼로리로 요리정렬 .map(Dish::getName) //요리명 추출 .collect(toList()); // 모든 요리명을 리스트에 저장
- 위 코드처럼 stream()을 parallelStream()으로 바꾸면 멀티코어 아키텍처에서 병렬로 실행할 수 있다.
- 선언형으로 코드를 구현할 수있어 루프와 if 조건문 등의 제어 블록을 사하여 어떤게 동작을 구현할지 지정할 필요 없이 저칼로리의 요리만 선택하라 같은 동작의 수행을 지정할 수 있다.
- 선언형 코드와 동작 파라미터화를 활용하면 변하는 요구사항의 쉽게 대응할 수 있다.
- filteer, sorted, map, collect 같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들어 연결하면 가독성과 명확성이 유지된다.
- 위 같은 연산들은 고수준 빌딩 블록으로 이루어져 있으므로 특정 스레딩 모델에 제한되지 않고 자유롭게 어떤 상황에서든 사용할 수 있다.
- 결과적으로 데이터 처리 과정을 병렬화 하면서 스레드와 락을 걱정할 필요가 없다.
- 스트림을 정리하자면 다음처럼 요약이 가능하다.
- 선언형 : 더 간결하고 가독성이 좋아진다.
- 조립할 수 있음 : 유연성이 좋아진다.
- 병렬화 성능이 좋아진다.
스트림 이란
- 정의
- 연속된 요소
- 컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연석된 값 집합의 인터페이스를 제공한다.
- 컬렉션은 자료구조이므로 시간과 공간의 복잡성과 관련된 요소 저장 및 접근 연선이 주를 이루고 스트림은 filter, sorted, map처럼 표현 계산식이 주를 이룬다. 컬렉션의 주제는 데이터고 스트림의 주제는 계산이다.
- 소스
- 스트림은 컬렉션, 배열, I/O 자원 등의 데이터 제공 소스로부터 데이터를 소비한다.
- 데이터 처리 연산
- 스트림은 함수형 프로그래밍 언어에서 일반적으로 지원하는 연산과 데이터베이스와 비슷한 연산을 지원한다.
- 연속된 요소
- 특징
- 파이프라이닝
- 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프 라인을 구성할 수 있도록 스트림 자신을 반환한다. 그 덕에 게으름, 쇼트서킷 같은 최적화도 얻을 수 있다.
- 내부 반복
- 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원한다.
- 파이프라이닝
- filter
- 람다를 인수로 받아 스트림에서 특정 요소를 제외시킨다.
- d -> d.getCalories() > 300
- map
- 람다를 이용해서 한 요소를 다른 요소로 변환하거나 정보를 추출한다.
- Dish::getName(메서드 참조), d -> d.getName(람다)
- limit
- 정해진 개수 이상의 요소가 스트림에 저장되지 못하게 스트림 크기를 축소 truncate한다.
- collect
- 스트림을 다른 형식으로 변환한다
스트림과 컬렉션
- 스트림과 컬렉션의 차이
- 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조이다. 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다.
- 스트림은 이론적으로 요청할 때만 요소를 계산하는 고정된(요소추가X, 요소제거X) 자료구조이다. 사용자가 데이터를 요청할 때만 값을 계산한다.
- 반복자와 마찬가지로 스트림도 한번만 탐색할 수 있다. 탐색된 스트림의 요소는 소비된다.
- 외부 반복과 내부 반복
- 외부 반복
- 컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야 한다. ex) for-each
- 내부 반복
- 스트림 라이브러리는 반복을 알아서 처리하고 결과 스트림 값을 어딘가에 저장해준다.
- 외부 반복
스트림 연산
- 중간연산
- filter나 sorted 같은 중간 연산은 다른 스트림을 반환한다.
- 여러 중간 연산을 연결해서 질의를 만들 수 있다.
- 중요한 특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것, 즉 게이르다는 것이다.
- 파이프라인을 통해 filter나 map은 서로 다른 연산이지만 한과저응로 병합된다 이를 루프 퓨전이라고 한다.
- 최종연산
- 스트림 파이프 라인에서 결과를 도출한다. 보통 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환된다.
- 스트림 이용과정
- 질의를 수행할(컬렉션 같은) 데이터 소스
- 스트림 파이프라인을 구성할 중간 연산 연결
- 스트림 파이프라인을 실행하고 결과를 만들 최종 연산
- 중간 연산
- filter : Predicate
, T -> boolean - map : Function<T,R> T-> R
- limit
- sorted : Comparator
, (T, T) -> int - distinct
- filter : Predicate
- 최종연산
- forEach : 스트림의 각 요소를 소비하면서 람다를 적용(void)
- count : 스트림 요소 개수를 반환한다(long)
- collect : 스트림을 리듀스해서 리스트, 맵, 정수 형식의 컬렉션을 만든다.
필터링
- 프레디케이트로 필터링
- filter 메서드는 프레디케이트를 인수로 받아서 프레디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환한다.
- 고유 요소 필터링
- 스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드도 지원한다.
- 고유 여부는 스트림에서 만든 객체의 hashCode, equals로 결정된다.
스트림 슬라이싱
- 프레디케이트를 이용한 슬라이싱
- 자바 9는 스트림의 요소를 효과적으로 선택할 수 있도록 takeWhile, dropWhile 두가지 새로운 메서드를 지원한다.
- TAKEWHILE 활용
- 아무 많은 요소를 포함하는 큰 스트림에서 takeWhile 연산을 이용히면 간단하게 처리할 수 있다.
- 무한 스트림을 포함한 모든 스트림에 프레디케이트를 적용해 스트림을 슬라이스 할 수 있다.
- List
sliceMenu1 = specialMenu.stream().takeWhile(dish -> dish.getCalories < 320).collect(toList())
- DROPWHILE 활용
- takeWhile연산으로 처리된 요소가 아닌 나머지 320 보다 큰 요소는 dropWhile을 이용해 작업을 완료할 수 있다.
- List
sliceMenu1 = specialMenu.stream().dropWhile(dish -> dish.getCalories < 320).collect(toList()) - dropWhile은 takeWhile과 정반대의 작업을 수행한다. 프레디케이트가 청므으로 거짓이 되는 지점까지 발견된 요소를 버린다. 프레디케이트가 거짓이 되면 그 지점에서 작업을 중단하고 남은 모든 요소를 반환한다. 무한 스트림에서도 동작 가능
- 스트림 축소
- 스트림은 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 limit(n) 메서드를 지원한다.
- List
sliceMenu1 = specialMenu.stream().filter(dish -> dish.getCalories < 320).limit(3).collect(toList()) - 정렬되지 않은 스트림에서도 사용할 수 있으며 정렬되지 않다면 결과도 정렬되지 않는다
- 요소 건너뛰기
- 스트림은 n개 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원한다. n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환된다.
- List
sliceMenu1 = specialMenu.stream().filter(dish -> dish.getCalories > 300).skip(2).collect(toList()) - 300 이상의 처음 두개의 요소를 건너뛴 다음에 나머지를 반환한다.
매핑
- 스트림의 각 요소에 함수 적용하기
- 스트림은 함수를 인수로 받는 map 메서드를 지언한다. 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.
- 아래의 1번 코드의 getName은 문자열을 반환하므로 map 메서드의 출력 스트림은 Stream
형식을 갖는다. - 아래의 2번 코드는 map 메서드를 연결 하여 문자열을 받아 각 문자열의 길이를 받아 온다.
//1 List<String> dishName = menu.stream().map(Dish::getName).collect(toList()); //2 List<Integer> dishNameLengths = menu.stream().map(Dish::getName).map(String::length).collect(toList());
스트림 평면화
- Arrays.Stream
words.stream().map(word -> word.split("")).map(Arrays::stream).distinct().collect(toList());
- flatMap
words.stream().map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(toList());
검색과 매칭
- anyMatch
- 프레디케이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인할때 사용한다, 최종적으로 불리언을 반환한다.
// menu에 채식요리가 있는지 확인 boolean result = menu.stream().anyMatch(Dish::isVegetarian);
- 프레디케이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인할때 사용한다, 최종적으로 불리언을 반환한다.
- allMatch
- 모든 요소가 주어진 프레디케이트와 일치하는지 검사한다.
// 모든 요리가 1000 칼로리 이하일 경우 boolean isHealthy = menu.stream().allMatch(dish ->dish.getCalories() < 1000);
- 모든 요소가 주어진 프레디케이트와 일치하는지 검사한다.
- noneMatch
- allMatch와 반대
// 모든 요리의 칼로리가 1000과 같거나 크지 않을때 boolean isHealthy = menu.stream().noneMatch(dish ->dish.getCalories() >= 1000);
- allMatch와 반대
- findAny
- 현 스트림에서 임의의 요소를 반환한다. 스트림연산과 연결을 사용할수 있으며 filter를 이용해 특정 요소만 선택할 수 있다.
- Optional : Optional
클래스는 값의 존재나 부재 여부를 표현하는 컨터이너 클래스로 아무 요소도 반환 하지 않을수있다. null은 쉽게 에러를 일으킬 수 있어 해당 클래스를 사용한다. - isPresent() : 값을 포함하면 true, 아닐경우 false를 반환
- isPresent(Consumer
block) : 값17있으면 주어진 블172실행한다. Consumer 함수형 인터페이스에는 T 형식의 인수를 받으며 void를 반환하는 람다를 전달 할 수 있다. - T get() : 값17존재하면 값을 반환하고 값이 없으면 NoSuchElementException을 일으킨다.
- T orElse(T other) : 값이 있으면 값을 반환하고 없으면 기본값을 반환한다.
Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();
- findFirst
- 첫번쨰 요소를 찾을 수 있다.
- 병렬실행에서는 첫번째 요소를 찾기가 어려워 요소의 반환 순서가 상관없으면 병렬 스트림에서는 제약이 적은 findAny를 사용하는게 좋다.
병렬 스트림
- parallelStream을 호출하면 병렬 스트림이 생성되고 병렬 스트림이란 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분활한 스트림이다.
- 병렬 스트림을 이요하면 모든 멀티코어 프로세서가 각각의 청크를 처리하도록 할당할 수 있다.
public long parallelSum(long n) { return Stream.iterate(1L, i -> i +1) // 무한 자연수 스트림 생성 .limit(n) .parallel() // 스트림을 병렬 스트림으로 변환 .reduce(0L, Long::sum); }
- 위 코드는 리듀싱 연산으로 스트림의 모든 숫자를 더한다. 일반 스트림과의 다른 점은 스트림이 여러 청크로 분할되어 있다는 것이다.
- 순차 스트림에 parallel을 호출해도 스트림 자체에는 아무 변화도 일어나지 않는다. 내부적으로는 prallel을 호출하면 이후 연산이 병렬로 수행해야 함을 의미하는 불리언 플래그거 설전된다. 반대로 sequential로 병렬 스트림을 순차 스트림으로 바굴 수 있어 중간연산에 따라 선택해서 사용할 수 있다.
- 최종적으로 파이프라인의 마지막 호출에 따라 전체적으로 어떠헤 실행될지도 결정된다.
- 병렬 스트림에서 사용되는 스레드 풀 설정
- 병렬 스트림은 내부적으로 ForkJoinPool을 사용한다.
- 스트림의 성능 측정
- 병렬화를 이용하면 순차나 반복 형식에 비해 성능이 더 좋아질 거라고 추축은 가능하지만 실제 측정을해서 확인해야 정확하다는 걸 알아야 한다.
숫자형 스트림
- MapToInt
- 각 요소들을 추출한 다음에 IntStram을 반환한다. IntStream 인터페이스에서 제공하는 sum은 기본값 0을 반환한다. max, min, average 등.. 사용가능
- 객체 스트림으로 복원
- boxed 메서드를사용하여 특화 스트림을 일반 스트림으로 변환 할 수 있다.
``` java
// 스트림을 숫자 스트림으로 변환
IntStream intStream = menu.stream().mapToInt(Dish::getCalories)
// 숫자 스트림을 스트림으로 변환
Strean
stream = intStrea.voxed();
- boxed 메서드를사용하여 특화 스트림을 일반 스트림으로 변환 할 수 있다.
``` java
// 스트림을 숫자 스트림으로 변환
IntStream intStream = menu.stream().mapToInt(Dish::getCalories)
// 숫자 스트림을 스트림으로 변환
Strean
//실제 최댓값가 스트림 요소가 없는 상황을 구분할땐 OptionalInt를 사용하여 최대값 요소를 찾을 수 있다. OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max(); // 값이 없을 때 기본 최댓값을 명시적으로 설정 int max = maxCalories.orElse(1);
- 숫자 범위
``` java
// 1 ~ 100으 범위를 나타냄, rangeClosed가 아닌 range 사용시 1과 100을 포함하지 않음
IntStream eventNumbers = Intstream.rageClosed(1, 100).filter(n -> n % 2 == 0);
스트림 만들기
- 값으로 스트림 만들기
- 임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용해서 스트림을 만들 수 잇다.
Stream<String> stream = Stream.of("Modern ", "java ", "In ", "Action "); // 스트림 비우기 Stream<String> emptyStram = Stream.empty();
- 임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용해서 스트림을 만들 수 잇다.
- null이 될 수 있는 객체로 스트림 만들기(Java 9)
Stream<String> homeValuesStream = Stream.ofNullable(System.getProperty("home")); //flatMap으로 활용 Stream<String> values = Stream.of("config", "home","user") .flatMap(key -> Stream.ofNullable(System.getProperty(key)));
- 배열로 스트림 만들기
int numbers = {1,2,3,4,5,6,7}; // 스트림 변환 후 합계 구하기 Arrays.stream(number).sum()
댓글남기기