Stream API 소개
SE8에 람다와 함께 추가된 기능이다.
Collection 프레임워크를 도와주는/어시스트하는? 프레임워크라고 생각하줄 수 있다
컬렉션에 스트림을 열어서 그 안에 있는 데이터들을 프로세싱 해 줄 수 있다
컬렉션뿐만아니라 입출력 소스에도 사용할 수 있다.
Map-Filter-Reduce
Stream API 를 사용하기전 Map-Filter-Reduce에 대해 먼저 공부해보자
List<Sale> sales = ...; // this is the list of all the sales
int amountSoldInMarch = 0;
for (Sale sale: sales) {
if (sale.getDate().getMonth() == Month.MARCH) {
amountSoldInMarch += sale.getAmount();
}
}
System.out.println("Amount sold in March: " + amountSoldInMarch);
해당 코드를 한번 들여다 보자
첫번째 단계인 if문을 보면 세일 오브젝트의 월이 3월인지를 체크한다. 이 단계에서 내가 프로세스할 데이터들을 분류 시켜주기때문에 Filtering 단계라고 해준다.
두번째 단계에서는 Sale오브젝트의 한 부분인 Amount 속성만 사용하기 위해 Sale객체를 int 객체로 매핑해주는 단계라고한다.
마지막단계는 내가 원하는 기준에 맞는 Sale 객체의 amount들을 하나로 더해준다. 여러개의 amount를 하나로 집계해주기 때문에 해당 단계를 Reducing 단계라고 해준다.
Mapping은 One to One transformation이다. 10개의 오브젝트가 담긴 리스트를 매핑해주면 10개의 변형된 오브젝트가 담긴 리스트가 나온다. Mapping은 순서를 지켜준다.
Filtering은 해당 오브젝트를 변경시키거나 하지 않고 여러개의 오브젝트들중에서 몇개를 고르는 과정이다. 따라서 타입을 바꾸지 않고 갯수를 바꾼다고 볼 수 있다. 필터링은 Predicate 함수형 인터페이스로 아무타입을 인풋으로 받아 boolean 값을 반환한다.
Reducing은 SQL aggregation과 같다고 볼 수 있다. COUNT, AVG와 같이 여러개의 데이터를 묶어서 결과로 표출해준다.
Map-Filter-Reduce 알고리즘 최적화
List<City> cities = ...;
int sum = 0;
for (City city: cities) {
int population = city.getPopulation();
if (population > 100_000) {
sum += population;
}
}
System.out.println("Sum = " + sum);
해당코드를 Collection을 리턴하는 가상의 map, filter 로 바꿔보자
int sum = cities.map(city -> city.getPopulation())
.filter(population -> population > 100_000)
.sum();
Collection<Integer> populations = cities.map(city -> city.getPopulation());
Collection<Integer> filteredPopulations = populations.filter(population -> population > 100_000);
int sum = filteredPopulations.sum();
만약 도시가 1000개라면 map 부분에서 1000개의 Integer를 반환시킨다.
그리고 filter 부분에서 map에서 반환 된ㄷ 1000개의 Integer를 하나씩 또 보면서 분류에 맞는 데이터를 남긴다
For문을 사용한 코드와는 다르게 중간 단계의 컬렉션 객체를 저장해야한다. 살펴봐야 할 객체가 많아질수록 이 단점은 극대화 된다.
이런 점 때문에 map과 filter는 Collection인터페이스에 추가되지 않고 Stream인터페이스에 추가되었다.
Stream<City> streamOfCities = cities.stream();
Stream<Integer> populations = streamOfCities.map(city -> city.getPopulation());
Stream<Integer> filteredPopulations = populations.filter(population -> population > 100_000);
int sum = filteredPopulations.sum(); // in fact this code does not compile; we'll fix it later
위의 코드와 같이 자바에서 map과 filter는 스트림을 반환해준다.
위 코드가 정상적으로 작동하려면 stream 에는 데이터가 저장되면 안된다.
스트림은 데이터를 저장하지 않는 객체이다.
Stream 잘 사용하기
스트림에서는 하나의 메서드만 호출할 수 있습니다. 따라서 스트림을 필드나 지역 변수에 저장하는 것은 쓸모없고 때로는 위험할 수 있습니다. 스트림을 인수로 받는 메서드를 작성하는 것도 위험할 수 있습니다. 스트림이 이미 처리되지 않았다는 보장이 없기 때문입니다. 스트림은 생성되고 즉시 소비되어야 합니다.
Intermediate Operation(중간연산)과 Terminal Operation(종결연산)
Stream API는 두가지 작업 유형으로 분리된다
- Intermediate Operation
- 중간연산에는 하나의 메서드만을 호출해야하고 체이닝을 통하여 중간 연산을 연속적으로 적용할 수 있다.
- 중간연산의 특성중 하나는 Lazy하다는 것이다. 중간연산은 종결 연산이 호출될때까지 실행되지 않는다.
- 중간연산은 스트림을 처리하고 변환하지만 최종 결과를 생성하지는 않는다.
- filter, map(mapToInt, mapToLong, mapToDouble), flatMap(flatMapToInt, …), distinct, sorted, peek, limit, skip 과 같이 여러 메서드가 중간연산에 속한다.
- Terminal Operation
- 스트림의 요소를 consume 한다
- 종결연산은 스트림을 처리하여 최종 결과를 생성한다. 종결연산 이후에는 스트림을 더 이상 사용할 수 없다.
- forEach, toArray, reduce, collect, min, max, count, anyMatch/allMatch/noneMatch 와 같이 여러 종결연산 메서드가 있다.
참고 (Reference)
Stream API 소개
SE8에 람다와 함께 추가된 기능이다.
Collection 프레임워크를 도와주는/어시스트하는? 프레임워크라고 생각하줄 수 있다
컬렉션에 스트림을 열어서 그 안에 있는 데이터들을 프로세싱 해 줄 수 있다
컬렉션뿐만아니라 입출력 소스에도 사용할 수 있다.
Map-Filter-Reduce
Stream API 를 사용하기전 Map-Filter-Reduce에 대해 먼저 공부해보자
List<Sale> sales = ...; // this is the list of all the sales
int amountSoldInMarch = 0;
for (Sale sale: sales) {
if (sale.getDate().getMonth() == Month.MARCH) {
amountSoldInMarch += sale.getAmount();
}
}
System.out.println("Amount sold in March: " + amountSoldInMarch);
해당 코드를 한번 들여다 보자
첫번째 단계인 if문을 보면 세일 오브젝트의 월이 3월인지를 체크한다. 이 단계에서 내가 프로세스할 데이터들을 분류 시켜주기때문에 Filtering 단계라고 해준다.
두번째 단계에서는 Sale오브젝트의 한 부분인 Amount 속성만 사용하기 위해 Sale객체를 int 객체로 매핑해주는 단계라고한다.
마지막단계는 내가 원하는 기준에 맞는 Sale 객체의 amount들을 하나로 더해준다. 여러개의 amount를 하나로 집계해주기 때문에 해당 단계를 Reducing 단계라고 해준다.
Mapping은 One to One transformation이다. 10개의 오브젝트가 담긴 리스트를 매핑해주면 10개의 변형된 오브젝트가 담긴 리스트가 나온다. Mapping은 순서를 지켜준다.
Filtering은 해당 오브젝트를 변경시키거나 하지 않고 여러개의 오브젝트들중에서 몇개를 고르는 과정이다. 따라서 타입을 바꾸지 않고 갯수를 바꾼다고 볼 수 있다. 필터링은 Predicate 함수형 인터페이스로 아무타입을 인풋으로 받아 boolean 값을 반환한다.
Reducing은 SQL aggregation과 같다고 볼 수 있다. COUNT, AVG와 같이 여러개의 데이터를 묶어서 결과로 표출해준다.
Map-Filter-Reduce 알고리즘 최적화
List<City> cities = ...;
int sum = 0;
for (City city: cities) {
int population = city.getPopulation();
if (population > 100_000) {
sum += population;
}
}
System.out.println("Sum = " + sum);
해당코드를 Collection을 리턴하는 가상의 map, filter 로 바꿔보자
int sum = cities.map(city -> city.getPopulation())
.filter(population -> population > 100_000)
.sum();
Collection<Integer> populations = cities.map(city -> city.getPopulation());
Collection<Integer> filteredPopulations = populations.filter(population -> population > 100_000);
int sum = filteredPopulations.sum();
만약 도시가 1000개라면 map 부분에서 1000개의 Integer를 반환시킨다.
그리고 filter 부분에서 map에서 반환 된ㄷ 1000개의 Integer를 하나씩 또 보면서 분류에 맞는 데이터를 남긴다
For문을 사용한 코드와는 다르게 중간 단계의 컬렉션 객체를 저장해야한다. 살펴봐야 할 객체가 많아질수록 이 단점은 극대화 된다.
이런 점 때문에 map과 filter는 Collection인터페이스에 추가되지 않고 Stream인터페이스에 추가되었다.
Stream<City> streamOfCities = cities.stream();
Stream<Integer> populations = streamOfCities.map(city -> city.getPopulation());
Stream<Integer> filteredPopulations = populations.filter(population -> population > 100_000);
int sum = filteredPopulations.sum(); // in fact this code does not compile; we'll fix it later
위의 코드와 같이 자바에서 map과 filter는 스트림을 반환해준다.
위 코드가 정상적으로 작동하려면 stream 에는 데이터가 저장되면 안된다.
스트림은 데이터를 저장하지 않는 객체이다.
Stream 잘 사용하기
스트림에서는 하나의 메서드만 호출할 수 있습니다. 따라서 스트림을 필드나 지역 변수에 저장하는 것은 쓸모없고 때로는 위험할 수 있습니다. 스트림을 인수로 받는 메서드를 작성하는 것도 위험할 수 있습니다. 스트림이 이미 처리되지 않았다는 보장이 없기 때문입니다. 스트림은 생성되고 즉시 소비되어야 합니다.
Intermediate Operation(중간연산)과 Terminal Operation(종결연산)
Stream API는 두가지 작업 유형으로 분리된다
- Intermediate Operation
- 중간연산에는 하나의 메서드만을 호출해야하고 체이닝을 통하여 중간 연산을 연속적으로 적용할 수 있다.
- 중간연산의 특성중 하나는 Lazy하다는 것이다. 중간연산은 종결 연산이 호출될때까지 실행되지 않는다.
- 중간연산은 스트림을 처리하고 변환하지만 최종 결과를 생성하지는 않는다.
- filter, map(mapToInt, mapToLong, mapToDouble), flatMap(flatMapToInt, …), distinct, sorted, peek, limit, skip 과 같이 여러 메서드가 중간연산에 속한다.
- Terminal Operation
- 스트림의 요소를 consume 한다
- 종결연산은 스트림을 처리하여 최종 결과를 생성한다. 종결연산 이후에는 스트림을 더 이상 사용할 수 없다.
- forEach, toArray, reduce, collect, min, max, count, anyMatch/allMatch/noneMatch 와 같이 여러 종결연산 메서드가 있다.