스트림이란?
- 여태 컬렉션, 배열에 저장된 요소를 반복처리 하기 위해 for문을 사용하거나 iterator 반복자를 이용했다면, 또 다른 방법으로는 스트림을 사용할 수 있다.
- 스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다.
- List 컬렉션에서 요소를 반복처리 하기 위해 스트림을 사용
// List의 stream()메소드로 Stream객체를 얻음
Stream<String> stream = list.stream();
// forEach메소드로 요소를 어떻게 처리할 지 람다식으로 제공한다.
stream.forEach( item -> // item 처리);
특징
- 내부 반복자이므로 처리 속도가 빠르고 병렬 처리에 효율적이다.
- 람다식으로 다양한 요소 처리를 정의할 수 있다.
- 중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.
내부/외부 반복자
1. 외부 반복자
- for문과 iterator반복자는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리함
- 컬렉션의 요소를 외부로 가져오는 코드와, 처리하는 코드를 모두 개발자 코드가 가지고 있어야 한다.
2. 내부 반복자
- 스트림은 요소 처리방법을 컬렉션 내부로 주입시켜서 요소를 반복 처리함
- 개발자 코드에서 제공한 데이터 처리코드(람다식)을 가지고 컬렉션 내부에서 요소를 반복처리 한다.
- 멀티코어 cpu를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있다.
- 하나씩 처리하는 순차적 외부 반복자보다 효율적으로 요소를 반복시킬 수 있다.
중간 처리와 최종 처리
스트림은 하나 이상 연결될 수 있다. 컬렉션의 오리지널 스트림 뒤에 필터링 중간 스트림이 연결될 수 있고, 그 뒤에 매핑 중간 스트림이 연결그 다음 집계처리(최종처리)를 통해 결과를 도출힐 수 있다. 이렇게 스트림이 연결되어있는 것을 스트림 파이프라인이라고 한다.
:: 오리지널 스트림 --- 집계처리 사이의 중간 스트림들
- 필터링 중간 스트림: 최종 처리를 위해 요소를 걸어냄
- 매핑 중간 스트림: 요소를 변환 시킴
- 최종 처리: 중간처리에서 정제된 요소들을 반복하거나 집계(카운팅, 총합, 평균)작업을 수행함
:: 예시
// Student 스트림
Stream<Student> studentStream = list.stream();
// student객체를 score로 매핑(변환)
// student 객체를 getScore() 메소드의 리턴값으로 매핑
InStream scoreStream = studentStream.mapToInt( student -> student.getScore() );
//평균 계산
double avg = scoreStream.average().getAsDouble();
mapToInt() 메소드는 객체를 int값으로 매핑해서 IntStream으로 변환시킨다. 어떤 객체를 어떤 int값으로 매핑할 것인지는 람다식으로 제공해야 한다. 따라서 student.getScore()을 사용하여 student 객체를 getScore()의 리턴값으로 매핑(변환)한다.
// 메소드 체이닝 패턴으로 간단하게 작성
double avg = list.stream()
.mapToInt(student -> student.getScore())
.average()
.getAsDouble();
파이프라인 맨 끝에는 반드시 최종 처리 부분이 존재해야한다. 없으면 오리지널과 중간처리 스트림은 작동하지 않는다. average()이하를 생략하면 stream, mapToInt는 동작하지 않는 것임.
리소스로부터 스트림 얻기
- java.util.stream 패키지에는 스트림 인터페이스들이 있다. BaseStream 인터페이스를 상위로 하는 하위 인터페이스들도 있다.
- baseStream이 공통이고, 그 아래 Stream, IntStream 등등은 각각의 primitive타입들을 처리하는 스트림임.
- java.util.Collection 인터페이스는 스트림과 parallelstream()메소드를 가지고 있기 때문에 자식 인터페이스인 List와 Set인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.
배열로부터 스트림 얻기
숫자 범위로부터 스트림 얻기
IntStream 또는 LongStream의 정적 메소드인 range()와 rangeClosed()메소드를 이용하여 특정 범위의 정수 스트림을 얻을 수 있다.
첫번째 매개값은 시작수, 두번쨰 매개값은 끝수, 끝수를 포함하지 않으면 range(), 포함하면 rangeClosed()를 사용한다.
파일로부터 스트림 얻기
java.nio.Files의 lines() 메소드를 이용하면 텍스트 파일의 행 단위 스트림을 얻을 수 있다. 이는 텍스트 파일에서 한 행씩 읽고 처리할 때 사용할 수 있다.
요소 걸러내기(필터링)
필터링은 요소를 걸러내는 중간처리 기능이다. 필터랑 메소드에는 distinct(), filter()가 있다.
- distinct() : 요소의 중복을 제거한다.
- 객체 스트림일 경우, equals()메소드의 리턴갓이 true면, 동일한 요소로 판단한다. int, double, longStream은 같은 값일 경우 중복을 제거한다.
- B A B A -> distinct() -> B A
- filter() : 메소드는 매개값으루 주어진 predicate가 true를 리턴하는 요소만 필터링한다.
- C B A -> filter() :: A=true, B=false, C=true -> C A
- Predicate는 함수형 인터페이스로, 매개값을 조사한 후 boolean을 리턴하는 test()메소드를 가지고 있다.
// Predicate<T> 람다식으로 표현하기
T -> {... return true}
or
T -> true; // return문만 있을 경우, 중괄호와 return키워드 생략 가능
요소 변환(매핑)
- 매핑은 스트림의 요소를 다른 요소로 변환하는 중간처리 기능이다. 매핑 메소드는 mapXxx(), asDoubleStream(), asLongStream(), boxed(), floatMapXxx()등이 있다.
요소를 다른 요소로 변환
- mapXxx()메소드는 요소를 다른 요소로 변환한 새로운 스트림을 리턴한다.
- A B -> mapXxx() :: A > D, B > C -> D C
- mapXxx()메소드의 종류가 있는데.. 매개타입으로 Function을 받는다. 이 매개타입은 함수형 인터페이스로, 각각의 추상메소드를 가지고 있다.
- 모든 Function은 매개값을 리턴값으로 매핑(변환)하는 applyXxx()메소드를 가지고 있다.
// Function<T,R>을 람다식으로 표현
T -> {... return R; }
or
T -> R; // return문만 있을 경우 중괄호와 return키워드 생략 가능
요소를 복수 개의 요소로 변환
- flatMapXxx()메소드 : 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림을 리턴한다.
- A B -> flatMapXxx() :: A -> A2, A1 / B-> B2, B1 -> A2 A1 B2 B1
요소 정렬
Comparable 구현 객체의 정렬
- 스트림의 요소가 객체일 경우 객체가 comparable을 구현하고 있어야만 sorted()메소드를 사용하여 정렬할 수 있다. 그렇지 않으면 ClassCastException 발생
요소를 하나씩 처리(루핑)
루핑 메소드: peek(), forEach()
요소 조건 만족 여부(매칭)
매칭은 요소들이 ㅌ측정 조건에 만족하는지 여부를 조사하는 최종 처리 기능이다.
요소 병렬 처리
동시성과 병렬성
- 데이터 동시성: 멀티 스레드가 하나의 코어에서 번갈아가며 실행하는것
- 데이터 병렬성: 멀티 스레드가 멀티 코어를 각각 이용해서 병렬로 실행하는 것을 말한다.
- 동시성은 한 시점에서 하나의 작업만 실행하는데, 워낙 작업 실행 속도가 빨라서 동시에 처리되는 것처럼 보이는 것이다.
- 병렬성은 한 시점에서 여러개의 작업을 병렬로 실행하기 때문에 동시성보다 좋은 성능을 낸다,