스트림 API란?
- 입출력 스트림과는 전혀 다른 개념
- 데이터를 추상화하여 다룬다.
- 다양한 방식으로 저장된 데이터를 읽고 쓰기 위한 공통된 방법을 제공한다.
- 배열이나 컬렉션뿐만 아니라 파일에 저장된 데이터도 모두 같은 방법으로 다룰 수 있게 된다.
- Java SE 8부터 제공
- 탄생 배경
- Java에서는 많은 양의 데이터를 저장하기 위해서 배열이나 컬렉션을 사용
- 저장된 데이터에 접근하기 위해서 반복문이나 반복자를 사용하여 매번 새로운 코드를 작성
- 이렇게 작성된 코드는 단점이 많음
- 길이가 너무 길다.
- 가독성이 떨어진다.
- 코드의 재사용이 거의 불가능하다.
- 데이터베이스의 쿼리와 같이 정형화된 처리 패턴이 없다.
- 데이터마다 다른 방법으로 접근해야 한다.
스트림 API의 특징
- 외부 반복을 통해 작업하는 컬렉션과는 달리 내부 반복(internal iteration)을 통해 작업을 수행한다.
- 재사용이 가능한 컬렉션과는 달리 단 한 번만 사용할 수 있다.
- 원본 데이터를 변경하지 않는다.
- 스트림의 연산은 필터-맵(filter-map) 기반의 API를 사용하여 지연(lazy) 연산을 통해 성능을 최적화한다.
- parallelStream() 메소드를 통한 손쉬운 병렬 처리를 지원한다.
스트림 API의 동작 흐름
- 스트림의 생성
- 스트림의 중개 연산 (스트림의 변환)
- 스트림의 최종 연산 (스트림의 사용)
flowchart LR
id1("데이터 소스") --> id2(["중개연산 (필터)"])
id2(["중개연산 (필터)"]) --> id3(["중개연산 (맵)"])
id3(["중개연산 (맵)"]) --> id4(("필터"))
스트림의 생성
- 생성 범위
- 컬렉션
- 배열
- 가변 매개변수
- 지정된 범위의 연속된 정수
- 특정 타입의 난수들
- 람다 표현식
- 파일
- 빈 스트림
Stream 클래스의 특징
- Stream 클래스의 forEach() 메소드는 해당 스트림의 요소를 하나씩 소모해가며
순차적으로 요소에 접근한다.- 같은 스트림으로는 forEach() 메소드를 한 번밖에 호출할 수 없다.
- 원본 데이터의 요소를 소모하는 것은 아니다.
- 같은 데이터에서 또 다른 스트림을 생성하여 forEach() 메소드를 호출하는 것은 가능하다.
컬렉션
- Collection 인터페이스에는 stream() 메소드가 정의되어 있다.
- Collection 인터페이스를 구현한 클래스들은 stream() 메소드로 스트림을 생성할 수 있다.
- parallelStream() 메소드를 사용하면 병렬 처리가 가능한 스트림을 생성할 수 있다.
- 사용 예시
배열
- Arrays 클래스에는 다양한 형태의 stream() 메소드가 클래스 메소드로 정의되어 있다.
- 기본 타입인 int, long, double 형을 저장할 수 있는 배열에 관한 스트림이 별도로 정의되어 있다.
- int
- IntStream
- long
- LongStream
- double
- DoubleStream
- int
- Arrays 클래스의 stream() 메소드는 전체 배열뿐만 아니라
배열의 특정 부분만을 이용하여 스트림을 생성할 수도 있다. - 사용 예시
가변 매개변수 (variable parameter)
- Stream 클래스의 of() 메소드를 사용하면 가변 매개변수를 전달받아 스트림을 생성할 수 있다.
- 사용 예시
지정된 범위의 연속된 정수
- 지정된 범위의 연속된 정수를 스트림으로 생성하기 위해
IntStream나 LongStream 인터페이스에는 range()와 rangeClosed() 메소드가 정의되어 있다. - 메소드
- range()
- 명시된 시작 정수를 포함하지만 명시된 마지막 정수는 포함하지 않는 스트림을 생성한다.
- rangeClosed()
- 명시된 시작 정수뿐만 아니라 명시된 마지막 정수까지도 포함하는 스트림을 생성한다.
- range()
- 사용 예시
특정 타입의 난수들
- 특정 타입의 난수로 이루어진 스트림을 생성하기 위해
Random 클래스에는 ints(), longs(), doubles()와 같은 메소드가 정의되어 있다.- 매개변수로 스트림의 크기를 long 타입으로 전달받을 수 있습니다.
- 매개변수를 전달받지 않으면 크기가 정해지지 않은 무한 스트림(infinite stream)을 반환한다.
- 이 때는 limit() 메소드를 사용하여 따로 스트림의 크기를 제한해야 합니다.
- 사용 예시
람다 표현식
- 람다 표현식을 매개변수로 전달받아
해당 람다 표현식에 의해 반환되는 값을 요소로 하는 무한 스트림을 생성하기 위해
Stream 클래스에는 iterate()와 generate() 메소드가 정의되어 있다. - iterate() 메소드는 시드(seed)로 명시된 값을 람다 표현식에 사용하여 반환된 값을
다시 시드로 사용하는 방식으로 무한 스트림을 생성한다. - generate() 메소드는 매개변수가 없는 람다 표현식을 사용하여 반환된 값으로 무한 스트림을 생성한다.
- 사용 예시
파일
- 파일의 한 행(line)을 요소로 하는 스트림을 생성하기 위해
java.nio.file.Files 클래스에는 lines() 메소드가 정의되어 있다. java.io.BufferedReader 클래스의 lines() 메소드를 사용하면 파일뿐만 아니라
다른 입력으로부터도 데이터를 행(line) 단위로 읽어 올 수 있다.- 사용 예시
빈 스트림
- 아무 요소도 가지지 않는 빈 스트림은 Stream 클래스의 empty() 메소드를 사용하여 생성할 수 있다.
- 사용 예시
스트림의 중개 연산 (intermediate operation)
- 스트림 API에 의해 생성된 초기 스트림은 중개 연산을 통해 또 다른 스트림으로 변환된다.
- 중개 연산은 스트림을 전달받아 스트림을 반환하므로 중개 연산은 연속으로 연결해서 사용할 수 있다.
- 스트림의 중개 연산은 필터-맵(filter-map) 기반의 API를 사용함으로 지연(lazy) 연산을 통해 성능을 최적화할 수 있다.
사용 목적 | 메소드 |
---|---|
스트림 필터링 | filter() distinct() |
스트림 변환 | map() flatMap() |
스트림 제한 | limit() skip() |
스트림 정렬 | sorted() |
스트림 연산 결과 확인 | peek() |
스트림 중개 연산 메소드
메소드 | 설명 |
---|---|
Stream | 해당 스트림에서 주어진 조건(predicate)에 맞는 요소만으로 구성된 새로운 스트림을 반환함. |
해당 스트림의 요소들을 주어진 함수에 인수로 전달하여, 그 반환값으로 이루어진 새로운 스트림을 반환함. | |
해당 스트림의 요소가 배열일 경우, 배열의 각 요소를 주어진 함수에 인수로 전달하여, 그 반환값으로 이루어진 새로운 스트림을 반환함. | |
Stream | 해당 스트림에서 중복된 요소가 제거된 새로운 스트림을 반환함. 내부적으로 Object 클래스의 equals() 메소드를 사용함. |
Stream | 해당 스트림에서 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림을 반환함. |
Stream | 결과 스트림으로부터 각 요소를 소모하여 추가로 명시된 동작(action)을 수행하여 새로운 스트림을 생성하여 반환함. |
Stream | 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소를 제외한 나머지 요소만으로 이루어진 새로운 스트림을 반환함. |
Stream Stream | 해당 스트림을 주어진 비교자(comparator)를 이용하여 정렬함. 비교자를 전달하지 않으면 영문사전 순(natural order)으로 정렬함. |
스트림 필터링
- 메소드
- filter() 메소드
- 해당 스트림에서 주어진 조건(predicate)에 맞는 요소만으로 구성된 새로운 스트림을 반환한다.
- distinct() 메소드
- 해당 스트림에서 중복된 요소가 제거된 새로운 스트림을 반환한다.
- 내부적으로 Object 클래스의 equals() 메소드를 사용하여 요소의 중복을 비교한다.
- filter() 메소드
- 사용 예시
스트림 변환
- 메소드
- map() 메소드
- 해당 스트림의 요소들을 주어진 함수에 인수로 전달하여, 그 반환값들로 이루어진 새로운 스트림을 반환한다.
- 해당 스트림의 요소가 배열이라면,
flatMap() 메소드를 사용하여 각 배열의 각 요소의 반환값을
하나로 합친 새로운 스트림을 얻을 수 있습니다.
- 해당 스트림의 요소가 배열이라면,
- 해당 스트림의 요소들을 주어진 함수에 인수로 전달하여, 그 반환값들로 이루어진 새로운 스트림을 반환한다.
- map() 메소드
- 사용 예시
스트림 제한
- 메소드
- limit() 메소드
- 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림을 반환한다.
- skip() 메소드
- 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소를 제외한
나머지 요소만으로 이루어진 새로운 스트림을 반환한다.
- 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소를 제외한
- limit() 메소드
- 사용 예시
스트림 정렬
- 메소드
- sorted() 메소드
- 해당 스트림을 주어진 비교자(comparator)를 이용하여 정렬한다.
- 비교자를 전달하지 않으면 기본적으로 사전 편찬 순(natural order)으로 정렬한다.
- 해당 스트림을 주어진 비교자(comparator)를 이용하여 정렬한다.
- sorted() 메소드
- 사용 예시
스트림 연산 결과 확인
- 메소드
- peek() 메소드
- 결과 스트림으로부터 요소를 소모하여 추가로 명시된 동작을 수행한다.
- 원본 스트림에서 요소를 소모하지 않으므로, 주로 연산과 연산 사이에 결과를 확인하고 싶을 때 사용한다.
- 개발자가 디버깅 용도로 많이 사용합니다.
- peek() 메소드
- 사용 예시
스트림의 최종 연산 (terminal operation)
- 스트림 API에서 중개 연산을 통해 변환된 스트림은 마지막으로 최종 연산을 통해 각 요소를 소모하여 결과를 표시한다.
- 지연(lazy)되었던 모든 중개 연산들이 최종 연산 시에 모두 수행된다.
- 최종 연산 시에 모든 요소를 소모한 해당 스트림은 사용할 수 없게 됩니다.
사용 목적 | 메소드 |
---|---|
요소의 출력 | forEach() |
요소의 소모 | reduce() |
요소의 검색 | findFirst(), findAny() |
요소의 검사 | anyMatch(), allMatch(), noneMatch() |
요소의 통계 | count(), min(), max() |
요소의 연산 | sum(), average() |
요소의 수집 | collect() |
스트림 최종 연산 메소드
메소드 | 설명 |
---|---|
void forEach(Consumer<? super T> action) | 스트림의 각 요소에 대해 해당 요소를 소모하여 명시된 동작을 수행함. |
Optional T reduce(T identity, BinaryOperator | 처음 두 요소를 가지고 연산을 수행한 뒤, 그 결과와 다음 요소를 가지고 또다시 연산을 수행함. 이런 식으로 해당 스트림의 모든 요소를 소모하여 연산을 수행하고, 그 결과를 반환함. |
Optional Optional | 해당 스트림에서 첫 번째 요소를 참조하는 Optional 객체를 반환함. (findAny() 메소드는 병렬 스트림일 때 사용함) |
boolean anyMatch(Predicate<? super T> predicate) | 해당 스트림의 일부 요소가 특정 조건을 만족할 경우에 true를 반환함. |
boolean allMatch(Predicate<? super T> predicate) | 해당 스트림의 모든 요소가 특정 조건을 만족할 경우에 true를 반환함. |
boolean noneMatch(Predicate<? super T> predicate) | 해당 스트림의 모든 요소가 특정 조건을 만족하지 않을 경우에 true를 반환함. |
long count() | 해당 스트림의 요소의 개수를 반환함. |
Optional | 해당 스트림의 요소 중에서 가장 큰 값을 가지는 요소를 참조하는 Optional 객체를 반환함. |
Optional | 해당 스트림의 요소 중에서 가장 작은 값을 가지는 요소를 참조하는 Optional 객체를 반환함. |
T sum() | 해당 스트림의 모든 요소에 대해 합을 구하여 반환함. |
Optional | 해당 스트림의 모든 요소에 대해 평균값을 구하여 반환함. |
<R,A> R collect(Collector<? super T,A,R> collector) | 인수로 전달되는 Collectors 객체에 구현된 방법대로 스트림의 요소를 수집함. |
요소의 출력
- 메소드
- forEach() 메소드
- 스트림의 각 요소를 소모하여 명시된 동작을 수행한다.
- 반환 타입이 void이므로 보통 스트림의 모든 요소를 출력하는 용도로 많이 사용한다.
- forEach() 메소드
- 사용 예시
요소의 소모
- 스트림의 최종 연산은 모두 스트림의 각 요소를 소모하여 연산을 수행하게 된다.
- 메소드
- reduce() 메소드
- 첫 번째와 두 번째 요소를 가지고 연산을 수행한 뒤, 그 결과와 세 번째 요소를 가지고 또다시 연산을 수행한다.
- 해당 스트림의 모든 요소를 소모하여 연산을 수행하고, 그 결과를 반환한다.
- 인수로 초깃값을 전달하면 초깃값과 해당 스트림의 첫 번째 요소와 연산을 시작하며,
그 결과와 두 번째 요소를 가지고 계속해서 연산을 수행한다. - 비어 있는 스트림과 reduce 연산을 할 경우 전달받은 초깃값을 그대로 반환해야 하기 때문에
반환 타입이 Optional가 아닌 T 타입이다.
- reduce() 메소드
- 사용 예시
요소의 검색
- 메소드
- findFirst() 메소드 & findAny() 메소드
- 해당 스트림에서 첫 번째 요소를 참조하는 Optional 객체를 반환한다.
- 비어 있는 스트림에서는 비어있는 Optional 객체를 반환한다.
- 병렬 스트림인 경우에는 findAny() 메소드를 사용해야만 정확한 연산 결과를 반환할 수 있다.
- 해당 스트림에서 첫 번째 요소를 참조하는 Optional 객체를 반환한다.
- findFirst() 메소드 & findAny() 메소드
- 사용 예시
요소의 검사
- 해당 스트림의 요소 중에서 특정 조건을 만족하는 요소가 있는지, 아니면 모두 만족하거나 모두 만족하지 않는지를 확인한다.
- 관련 메소드 모두 인수로 Predicate 객체를 전달받으며, 요소의 검사 결과는 boolean 값으로 반환한다.
- 메소드
- anyMatch()
- 해당 스트림의 일부 요소가 특정 조건을 만족할 경우에 true를 반환한다.
- allMatch()
- 해당 스트림의 모든 요소가 특정 조건을 만족할 경우에 true를 반환한다.
- noneMatch()
- 해당 스트림의 모든 요소가 특정 조건을 만족하지 않을 경우에 true를 반환한다.
- anyMatch()
- 사용 예시
요소의 통계
- 메소드
- count() 메소드
- 해당 스트림의 요소의 총 개수를 long 타입의 값으로 반환한다.
- max() 메소드
- 해당 스트림의 요소 중에서 가장 큰 값을 가지는 요소를 참조하는 Optional 객체를 얻을 수 있다.
- min() 메소드
- 해당 스트림의 요소 중에서 가장 작은 값을 가지는 요소를 참조하는 Optional 객체를 얻을 수 있다.
- count() 메소드
- 사용 예시
요소의 연산
- 각 메소드들은 IntStream이나 DoubleStream과 같은 기본 타입 스트림에 정의되어 있다.
- 메소드
- sum() 메소드
- 해당 스트림의 모든 요소에 대해 합을 구한다.
- average() 메소드
- 해당 스트림의 모든 요소에 대해 평균을 구한다.
- 각 기본 타입으로 래핑 된 Optional 객체를 반환합니다.
- sum() 메소드
- 사용 예시
요소의 수집
- Collectors 클래스에는 미리 정의된 다양한 방법이 클래스 메소드로 정의되어 있다.
- 사용자가 직접 Collector 인터페이스를 구현하여 자신만의 수집 방법을 정의할 수도 있다.
- 기본 메소드
- collect() 메소드
- 인수로 전달되는 Collectors 객체에 구현된 방법대로 스트림의 요소를 수집한다.
- collect() 메소드
- 수집 용도별 Collectors 메소드
- 스트림을 배열이나 컬렉션으로 변환
- toArray()
- toCollection()
- toList()
- toSet()
- toMap()
- 요소의 통계와 연산 메소드와 같은 동작을 수행
- counting()
- maxBy()
- minBy()
- summingInt()
- averagingInt()
- …
- 요소의 소모와 같은 동작을 수행
- reducing()
- joining()
- 요소의 그룹화와 분할
- groupingBy()
- partitioningBy()
- 스트림을 배열이나 컬렉션으로 변환
- 사용 예시