스트림 API 가 지원하는 다양한 연산을 살펴보자
1. 필터링
- filter
: Predicate 로 필터링
List<Beverage> beverageMenu = menu.stream()
.filter(Beverage::isTea) // 음료 종류가 차인지 확인하는 메소드 참조
.collect(toList());
Predicate
boolean을 반환하는 함수
- distinct
: 고유요소 필터링
List<Beverage> beverageMenu = menu.stream()
.filter(Beverage::isTea)
.distinct() // 중복을 '필터링'
.collect(toList());
2. 슬라이싱
- takeWhile
, dropWhile
: Predicate 를 이용한 슬라이싱 (JAVA 9)
filter 는 전체 스트림을 반복하면서 각 요소에 Predicate를 적용한다.
하지만 이미 정렬된 리스트라던가 특수한 상황일 땐 특정 조건이 나왔을 때 반복 작업을 중단할 수 있다. 요소가 많이 담긴 큰 스트림일 경우 filter를 쓸 때와 상당한 차이가 날 수 있다.
// takeWhile : Predicate를 만족하는 요소까지만 선택하고 나머진 버린다.
List<Beverage> filterdMenu
= specialMenu.stream()
.takeWhile(beverage -> beverage.getSize() < SIZE.GRANDE)
.collect(toList());
// dropWhile : Predicate를 만족하지 않는 지점까지 발견된 요소를 버리고 나머지를 선택한다.
List<Beverage> filterdMenu
= specialMenu.stream()
.dropWhile(beverage -> beverage.getSize() < SIZE.GRANDE)
.collect(toList());
- limit
: 스트림 축소
List<Beverage> beverageMenu = menu.stream()
.filter(Beverage::isTea)
.limit(3) // 3 이하의 크기를 갖는 새로운 스트림 반환
.collect(toList());
- skip
: 요소 건너뛰기
List<Beverage> beverageMenu = menu.stream()
.filter(Beverage::isTea)
.skip(2) // 종류가 차인 처음 두 음료를 건너뛰고 나머질 반환
.collect(toList());
3. 매핑
- map
: 스트림의 각 요소에 함수 적용하기
// beverage 객체 스트림을 getName으로 매핑하여 Steam<String>을 반환
List<String> beverageNames = menu.stream()
.map(Beverage::getName)
.collect(toList());
- flatMap
: 스트림 평면화
String words = "Hello"
words.stream()
.map(word -> word.split("")) // h e l l o 별개의 스트림으로 분할
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
4. 검색과 매칭
(1) 매칭
- anyMatch
: Predicate 가 적어도 한 요소와 일치하는지 확인
if(menu.stream().anyMatch(Beverage::isTea)) {
System.out.println("적어도 하나는 음료수가 차다!!");
}
- allMatch
: Predicate 가 모든 요소와 일치하는지 확인
if(menu.stream().allMatch(Beverage::isTea)) {
System.out.println("모든 음료수가 차다!!");
}
- noneMatch
: Predicate가 모든 요소와 일치하지 않는지 확인
if(menu.stream().noneMatch(Beverage::isTea)) {
System.out.println("모든 음료수가 차가 아니다!!");
}
매칭 메소드들은 스트림 쇼트 서킷 기법을 사용한다. 즉, 자바의 &&, || 와 같은 연산을 사용한다.
쇼트 서킷 Short-Circuit
여러 개의 조건이 중첩된 상황에서, 값이 결정나면 이후의 불필요한 연산을 진행하지 않고 조건문을 빠져나가 실행 속도를 높이는 기법
(2) 검색
- findAny
: 요소 검색
Optional<Beverage> beverage =
menu.stream()
.filter(Beverage::isTea)
.findAny();
- findFirst
: 첫번째 요소 찾기
Optional<Beverage> beverage =
menu.stream()
.filter(Beverage::isTea)
.findFirst();
Optional<T>
값의 존재 여부나 부재 여부를 표현하는 컨테이너 클래스
findAny, findFirst 의 경우 아무것도 반환하지 않을 수 있다. 즉, null을 반환하게 되는데 이는 에러를 쉽게 일으키는 요인 중 하나다.
Optional을 쓰면 아래와 같이 값이 없을 때 어떻게 처리할 지 강제할 수 있다.
// isPresent 는 값이 존재하면 true, 없으면 false를 반환한다
boolean presnetBeverage = menu.stream()
.filter(Beverage::isTea)
.findFirst()
.isPresent();
// ifPresent는 값이 있으면 Consumer<T>를 수행한다
menu.stream()
.filter(Beverage::isTea)
.findFirst()
.ifPresent(System.out.println(beverage.getName()));
// get은 값이 있으면 반환하고 없으면 NoSuchElementException을 반환한다.
Beverage beverage = menu.stream()
.filter(Beverage::isTea)
.findFirst()
.get();
// orElse는 값이 없으면 특정 값을 대신 반환하거나 특정 메소드를 수행할 수 있다
Beverage beverage = menu.stream()
.filter(Beverage::isTea)
.findFirst()
.orElse(new Beverage("우아한 아메리카노"));
// orElseThrow는 값이 없으면 특정 예외를 던진다
Beverage beverage = menu.stream()
.filter(Beverage::isTea)
.findFirst()
.orElseThrow(BeverageException::new)
5. reduce
: 리듀싱 (폴드)
- 요소의 합
// sum 변수의 초기값을 0으로 한다
int sum = numbers.stream().reduce(0, (a, b) -> a + b );
// 초기값이 없으면?
Optiona<Integer> sum = numbers.stream.reduce(0. Integer::sum);
- 최댓값과 최솟값
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
6. 실전.. 대신 간단 퀴즈
Q. Dish 객체는 int를 반환하는 getCalories 메소드를 가지고 있다. menu에 있는 Dish들의 칼로리 합을 구하는 옳은 스트림 구현을 모두 고르시오.
// 1번
int calories = menu.stream()
.map(Dish::getCalories)
.reduce(Integer::sum)
// 2번
int calories = menu.stream()
.map(Dish::getCalories)
.sum()
// 3번
int calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum)
// 4번
Optional<Integer> calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum)
// 5번
Optional<Integer> calories = menu.stream()
.map(Dish::getCalories)
.reduce(Integer::sum)
7. 숫자형 스트림
IntStream
, DoubleStream
, LongStream
: 기본형 특화 스트림
매핑, 박싱 비용을 피할 수 있도록 각 타입 요소에 특화된 stream
- mapToInt
: 숫자 스트림으로 매핑
int calories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();
- boxed
: 객체 스트림으로 복원하기
List<Integer> calories = menu.stream()
.mapToInt(Dish::getCalories)
.boxed()
.collect(toList());
- range
, rangeClosed
: 숫자 범위
// 1과 100을 포함하지 않는다 -> 49개의 요소 반환
IntStream evens = IntStream.range(1,100)
.filter(n -> n % 2 == 0);
// 1과 100을 포함한다 -> 50개의 요소 반환
IntStream evens = IntStream.rangeClosed(1,100)
.filter(n -> n % 2 == 0);
8. 스트림 만들기
- Stream.of
: 값으로 스트림 만들기
// 직접 값을 넣어 스트림 만들기
Stream<String> pairs = Stream.of("choonsik", "papi", "mark");
// 스트림 비우기
Stream<String> pairs = Stream.empty();
- Stream.ofNullable
: null이 될 수 있는 객체로 스트림 만들기
Stream<String> studyCrews =
allCrews.filter(Crew::isStdyingJava)
.map(Crew::getName)
.flatMap(crew -> Stream.ofNullable(crew)));
- Arrays.stream
: 배열로 스트림 만들기
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers)
.sum();
- File.lines
: 파일로 스트림 만들기
// 주어진 파일의 행 스트림을 문자열로 반환
Stream<String> lines =
Files.line(Paths.get("data.txt"), Charset.forName("utf-8"));
(+) Files.list 는 디렉토리에서 목록을 읽어와 스트림으로 반환한다
String path = "D:/";
Stream<Path> dir = Files.list(Paths.get(path));
- Stream.iterate
, Stream.generate
: 함수로 무한 스트림(=언바운드 스트림) 만들기
Stream.iterate(0, n -> n + 2)
.limit(10) // 보통 무한한 값을 출력하지 않도록 limit를 연결해 사용한다
.forEach(System.out::println);
Stream.generate(Math::random) // generate는 Supplier<T>를 인수로 받아 값을 생산한다
.limit(5)
.forEach(System.out::println);
참고
[ 모던 자바 인 액션 ] Ch5. 스트림 활용
'자바 뚝딱거리기' 카테고리의 다른 글
[JPA] JPA는 왜 써야할까? (1. JPA 소개) (0) | 2021.06.24 |
---|---|
[Spring] @ConstructorProperties는 꼭 필요한가요? (feat.Lombok) (2) | 2021.04.28 |
[JAVA] 스트림 소개 (3) | 2021.03.15 |
[JAVA] 람다 표현식 (0) | 2021.03.01 |
[JAVA] 동작 파라미터화 코드 전달하기 (0) | 2021.02.26 |