Java 8버전 이상부터는 Stream API를 지원한다
자바에서도 8버전 이상부터 람다를 사용한 함수형 프로그래밍이 가능해졌다.
기존에 존재하던 Collection과 Stream은 무슨 차이가 있을까? 바로 '데이터 계산 시점'이다.
먼저, 어부가 어류 중에서도 고등어를 잡고 싶어서 그물로 고등어를 잡았습니다. 이 행위를 filter라고 하고, 이 연산자를 중간 연산자라고 합니다.그리고 고등어를 포장하지 않고 생으로 팔 수는 없기 때문에 상자에 담아야 합니다. 이 행위를 map이라고 하고, 이 연산자도 마찬가지로 중간 연산자라고 합니다.
마지막으로, 고등어가 실린 수많은 상자를 운반하여 다른 곳으로 이동하면서 끝이 납니다. 이 행위를 collect라고 하고, 이 연선자는 최종 연산자라고 합니다.
어떤가요, 이제 조금 감이 잡히지 않나요? 스트림은 수많은 데이터의 흐름 속에서 각각의 원하는 값을 가공하여 최종 소비자에게 제공하는 역할을 한다고 보면 되겠습니다.
Collection은 핸드폰에 음악 파일을 미리 저장하여 재생하는 플레이어라면, Stream은 필요할 때 검색해서 듣는 멜론과 같은 음악 어플이라고 생각하면 된다.
Collection은 외부 반복
, Stream은 내부 반복
이라고 했다. 두 차이를 알아보자.
성능 면에서는 '내부 반복'이 비교적 좋다. 내부 반복은 작업을 병렬 처리하면서 최적화된 순서로 처리해준다. 하지만 외부 반복은 명시적으로 컬렉션 항목을 하나씩 가져와서 처리해야하기 때문에 최적화에 불리하다.
즉, Collection에서 병렬성을 이용하려면 직접 synchronized
를 통해 관리해야만 한다.
스트림은 연산 과정이 '중간'과 '최종'으로 나누어진다.
filter, map, limit
등 파이프라이닝이 가능한 연산을 중간 연산, count, collect
등 스트림을 닫는 연산을 최종 연산이라고 한다.
둘로 나누는 이유는, 중간 연산들은 스트림을 반환해야 하는데, 모두 한꺼번에 병합하여 연산을 처리한 다음 최종 연산에서 한꺼번에 처리하게 된다.
ex) Item 중에 가격이 1000 이상인 이름을 5개 선택한다.
List<String> items = item.stream()
.filter(d->d.getPrices()>=1000)
.map(d->d.getName())
.limit(5)
.collect(tpList());
filter와 map은 다른 연산이지만, 한 과정으로 병합된다.
만약 Collection 이었다면, 우선 가격이 1000 이상인 아이템을 찾은 다음, 이름만 따로 저장한 뒤 5개를 선택해야 한다. 연산 최적화는 물론, 가독성 면에서도 Stream이 더 좋다.
중간 연산은 모두 스트림을 반환한다.
(boolean) allMatch(Predicate) : 모든 스트림 요소가 Predicate와 일치하는지 검사
(boolean) anyMatch(Predicate) : 하나라도 일치하는 요소가 있는지 검사
(boolean) noneMatch(Predicate) : 매치되는 요소가 없는지 검사
(Optional) findAny() : 현재 스트림에서 임의의 요소 반환
(Optional) findFirst() : 스트림의 첫번째 요소
reduce() : 모든 스트림 요소를 처리해 값을 도출. 두 개의 인자를 가짐
collect() : 스트림을 reduce하여 list, map, 정수 형식 컬렉션을 만듬
reduce(): 스트림의 요소들을 결합하여 단일 결과를 생성하는 연산입니다. 예를 들어, 숫자의 리스트에서 모든 숫자의 합계를 구할 때 reduce()를 사용할 수 있습니다.
(void) forEach() : 스트림 각 요소를 소비하며 람다 적용
(Long) count : 스트림 요소 개수 반환
값의 존재나 여부를 표현하는 컨테이너 Class
map()
List<String> names = Arrays.asList("Sehoon", "Songwoo", "Chan", "Youngsuk", "Dajung");
names.stream()
.map(name -> name.toUpperCase())
.forEach(name -> System.out.println(name));
filter()
List<String> startsWithN = names.stream()
.filter(name -> name.startsWith("S"))
.collect(Collectors.toList());
reduce()
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));
sum : 55
collect()
System.out.println(names.stream()
.map(String::toUpperCase)
.collect(Collectors.joining(", ")));