[Java] 스트림(Stream)
스트림(Stream)?
스트림(Stream)은 Java 8 API에 새로 추가된 기능이다. 스트림을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있다.
여기서 선언형이란 데이터를 처리하는 구현 코드 대신, 질의로 표현하는 것을 말한다.
또 스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.
그럼 다음 예시 코드를 한번 보자.
List<Movie> highRatingMovies = new ArrayList<>();
for (Movie movie: movies) {
if (movie.getRating() > 7) {
highRatingMovies.add(movie);
}
}
Collections.sort(highRatingMovies, new Comparator<Movie>() {
public int compare(Movie movie1, Movie movie2) {
return Integer.compare(movie1.getRating(), movie2.getRating());
}
});
List<String> highRatingMoviesName = new ArrayList<>();
for (Movie movie: highRatingMovies) {
highRatingMoviesName.add(movie.getName());
}
위 코드는 Java 8 이전, 즉 스트림이 나오기 전 코드이다. 위 코드에서 평점을 필터링하고 정렬하고 이름을 얻는 로직이 구현되어 있다.
위 코드를 스트림으로 바꾸면 다음과 같이 작성할 수 있다.
List<String> highRatingMoviesName =
movies.stream()
.filter(m -> m.getRating() > 7) // 평점 7 이상 영화 선택
.sorted(comparing(Movie::getRating)) // 평점 순으로 정렬
.map(Movie::getName) // 영화명 추출
.collect(Collectors.toList()); // 리스트에 저장
특징
Stream은 다음과 같은 특징이 있다.
파이프라이닝
대부분의 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 반환한다.
때문에 laziness, short-circuiting 같은 최적화도 얻을 수 있다.
내부 반복
반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원한다.
Stream & Collection
자바의 기존 컬렉션과 새로운 스트림 모두 연속된 요소 형식의 값을 저장하는 자료구조의 인터페이스를 제공한다.
여기서 '연속된'이라는 표현은 순서와 상관없이 아무 값에나 접속하는 것이 아니라 순차적으로 값에 접근한다는 것을 의미한다.
Stream vs Collection
스트림과 컬렉션의 가장 큰 차이는 데이터를 언제 계산하느냐이다.
컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조다. 즉, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다.
반면 스트림은 이론적으로 요청할 때만 요소를 계산하는 고정된 자료구조이다.
여기서 스트림에 요소를 추가하거나 제거할 수 없기 때문에 고정된 자료구조라는 표현이 쓰였다.
또 컬렉션은 사용자가 직접 요소를 반복하는 외부 반속인 반면, 스트림은 내부에서 반복을 처리하고 결과를 저장해주는 내부 반복을 사용한다.
Stream 탐색
스트림은 이터레이터와 마찬가지로 한 번만 탐색할 수 있다. 즉, 탐색된 스트림의 요소는 소비된다.
한 번 탐색한 요소를 다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야 한다. 이러려면 컬렉션처럼 반복 사용할 수 있는 데이터 소스여야 한다. 만약 데이터 소스가 I/O 채널이라면 소스를 반복 사용할 수 없으므로 새로운 스트림을 만들 수 없다.
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> s = list.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); // 이미 스트림이 소비되어서 사용할 수 없다.
Stream 연산
스트림은 다음과 같이 두 가지 연산이 있다.
- 중간 연산: filter, map, sorted 등
- 최종 연산: collect, forEach 등
여기서 중간 연산은 연결할 수 있는 스트림 연산을 말하며, 최종 연산은 스트림을 닫는 연산을 말한다.
중간 연산
filter와 map 같은 중간 연산은 다른 스트림을 반환한다. 따라서 여러 중간 연산을 연결해서 질의를 만들 수 있다.
중간 연산의 중요한 특징은, 스트림은 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종 연산으로 한 번에 처리하기 때문에, 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하는 않는다는 것이다. 즉, lazy하다.
최종 연산
최종 연산은 스트림 파이프라인에서 결과를 도출한다. 보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환된다.
예를 들어, 다음 파이프라인에서 forEach는 각 영화들에 람다를 적용한 다음 void를 반환하는 최종 연산이다.
movies.stream().forEach(System.out::println);
'Language > Java' 카테고리의 다른 글
[Java] 스트림 연산 (0) | 2023.04.07 |
---|---|
[Java] HttpServlet (0) | 2023.04.06 |
[Java] 메서드 참조 (0) | 2023.04.04 |
[Java] 람다 타입 검사/추론/제약 (0) | 2023.04.04 |
[Java] 함수형 인터페이스 (0) | 2023.04.03 |
댓글
이 글 공유하기
다른 글
-
[Java] 스트림 연산
[Java] 스트림 연산
2023.04.07 -
[Java] HttpServlet
[Java] HttpServlet
2023.04.06 -
[Java] 메서드 참조
[Java] 메서드 참조
2023.04.04 -
[Java] 람다 타입 검사/추론/제약
[Java] 람다 타입 검사/추론/제약
2023.04.04