Modern Java In Action 정리
Modern Java In Action을 읽고 내용을 정리해본다.
5장 스트림 활용
필터링
filter()
메서드는Predicate<T>
를 인자로 일치하는 모든 요소를 포함하는 스트림을 반환한다.@Test public void 스트림_filter(){ List<Integer> numbers = Arrays.asList(1, 2, 3, 1, 2, 4); numbers.stream() .filter(i -> i % 2 == 0) // 짝수만 필터링 .distinct() // 중복요소 제거, hashCode와 equals로 결정된다. .forEach(System.out::println); // 출력 }
distinct()
,skip(n)
,limit(n)
와 같이 사용되어 스트림을 축소할 수 있다. 직관적으로 동작이 메서드명에 나타나므로 따로 정리를 하지 않는다.자바9에서 추가된
takeWhile()
,dropWhile()
를 활용하면 기본 filter에서 추가 동작을 지정할 수 있다.
(이미 정렬된 상태에서 유용하게 적용이 가능하다.)
@Test
public void 스트림_takeWhile_dropWhile(){
// 메뉴 컬렉션을 칼로리를 기준으로 오른차순 정렬
menu.sort(Comparator.comparing(Dish::getCalories));
menu.stream().forEach((dish -> System.out.print(dish.getName() + "(" + dish.getCalories() + ") ")));
// takeWhile() : 조건에 만족할 때까지 스트림을 반환, false시 반복 중단
List<Dish> lowCalDish = menu.stream()
.takeWhile(dish -> dish.getCalories() < 450)
.collect(Collectors.toList());
System.out.println("\n450 칼로리 미만 ------");
lowCalDish.stream().forEach((dish -> System.out.print(dish.getName() + "(" + dish.getCalories() + ") ")));
// dropWhile() : 조건에 만족하는 요소까지 버림, true부터 스트림을 반환
List<Dish> highCalDish = menu.stream()
.dropWhile(dish -> dish.getCalories() < 450)
.collect(Collectors.toList());
System.out.println("\n450 칼로리 이상 ------");
highCalDish.stream().forEach((dish -> System.out.print(dish.getName() + "(" + dish.getCalories() + ") ")));
}
chicken(400) salmon(450) french fries(530) beef(700) pork(800)
450 칼로리 미만 ------
chicken(400)
450 칼로리 이상 ------
salmon(450) french fries(530) beef(700) pork(800)
매핑
- 스트림API는 스트림의 요소에서 다른 요소로 변환할 수 있는 map()과 flatMap() 메서드를 제공한다.
<R> Stream<R> map(Function<? super T, ? extends R> var1);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> var1);
- 매핑 메서드들은 스트림의 한 요소(T 타입)에서 맵핑되는 새로운 타입(R 타입)의 요소를 만들어 반환한다.
- 아래 예시에서는
map(Function<String, Integer>)
를 이용하여 Integer를 요소로 갖는 스트림을 생성하고 이를 List로 반환했다.
@Test
public void map_테스트(){
List<String> strs = List.of("하이", "헬로우");
// 문자열 길이를 요소로 같는 리스트 생성
List<Integer> lengths = strs.stream()
.map(String::length)
.collect(Collectors.toList());
}
map()은 스트림의 요소별로 1대1로 반환하기 때문에 이해가 쉽지만 flatMap()은 다른 동작을 한다.
map()과 flatMap()에 파라미터로 주는 Function 인터페이스를 보면 반환형이 다른것을 볼 수 있다.
- map() :
Function<? super T, ? extends R>
- flatMap() :
Function<? super T, ? extends Stream<? extends R>>
- map() :
flatMap의 파라미터인 Function 인터페이스의 구현체는 반환하는 객체의 타입이 Stream 타입이고, flatMap은 Stream 그 자체가 아닌 Stream의 컨텐츠로 매핑한다. 즉, map과 다르게 flatMap은 하나의 평면화된 스트림을 반환한다.
차이를 볼 수 있게 예제 코드를 확인한다.
문자열 리스트에서 중복을 제거한 한글자가 요소인 리스트를 구한다.@Test public void flatMap_테스트(){ List<String> strs = List.of("HI", "HELLO"); List<String> distinctStrs = strs.stream() .map(s -> s.split("")) // Stream<String[]> // .map(sArr -> Arrays.stream(sArr)) // Stream<Stream<String>> .flatMap(sArr -> Arrays.stream(sArr)) // Stream<String> .distinct() .collect(Collectors.toList()); distinctStrs.forEach(System.out::println); // H I E L O }
sArr -> Arrays.stream(sArr) 의 실행결과인 Stream
- map은 Stream<Stream
>을 리턴
![ModernJava3_0]({{ "/assets/img/202002/ModernJava3_0.png" | relative_url }})
- flatMap은 Stream
의 컨텐츠인 String으로 하나의 평면화면 Stream 을 리턴했다.
distinct()도 의도한 대로 글자당 중복을 제거하도록 동작한다.
![ModernJava3_1]({{ "/assets/img/202002/ModernJava3_1.png" | relative_url }})
- 이 밖에도 flatMap은 아래와 같이 변환을 지원하므로 자세한 내용은 Mkyong flatMap Example 의 내용을 참고하자
Stream<String[]> -> flatMap -> Stream<String> Stream<Set<String>> -> flatMap -> Stream<String> Stream<List<String>> -> flatMap -> Stream<String> Stream<List<Object>> -> flatMap -> Stream<Object>
검색과 매칭
- 스트림API는 특정 속성이 데이터 집합에 있는지 여부를 검색하는 allMatch, anyMatch, noneMatch, findFirst, findAny 등 다양한 유틸리티 메서드를 제공한다.
@Test
public void 검색과_매칭(){
List<String> strs = List.of("HI", "HELLO");
boolean isAllStartWithH = strs.stream()
.allMatch(s -> s.startsWith("H"));
System.out.println("모두 H로 시작합니다. : " + isAllStartWithH);
boolean isOneEndWithO = strs.stream()
.anyMatch(s -> s.endsWith("O"));
System.out.println("한 요소 이상이 O로 끝납니다. : " + isOneEndWithO);
boolean isNoneEndWithK = strs.stream()
.noneMatch(s -> s.endsWith("K"));
System.out.println("모두 K로 끝나지 않습니다. : " + isNoneEndWithK);
Optional<String> first = strs.stream().findFirst();
System.out.println("첫번째 요소는(First) : " + first.get());
Optional<String> any = strs.stream().findAny();
System.out.println("첫번째 요소는(Any) : " + any.get());
}
- findFirst와 findAny가 모두 필요한 이유는 병렬성 때문이다. 요소의 반환순서가 상관없다면 병렬 스트림에서는 제약이 적은 findAny를 사용한다.
리듀싱
최종 결과값이 나올 때까지 스트림의 요소를 반복적으로 처리하는 연산을 리듀싱 연산이라고 한다.
리듀싱 연산은 reduce 메서드로 수행할 수 있으며 사용은 예제코드를 참고한다.
@Test public void reduce(){ int[] nums = new int[]{1, 2, 3, 4, 5}; // 초기값을 사용하여 reduce 연산 int sumWithInitVal = Arrays.stream(nums) .reduce(0, Integer::sum); System.out.println("합계 : " + sumWithInitVal); // 15 // 초기값 없이 reduce 연산 OptionalInt optSum = Arrays.stream(nums) .reduce(Integer::sum); System.out.println("합계 : " + optSum.getAsInt()); // 15 // 초기값이 없기 때문에 reduce 연산시 결과값이 없을 수 있다. OptionalInt optSum2 = Arrays.stream(new int[]{}) .reduce(Integer::sum); System.out.println("합계 : " + optSum2.orElse(0)); // 0 }
'책정리 > Modern Java In Action' 카테고리의 다른 글
[책 정리]Modern Java In Action 2장 (0) | 2020.05.18 |
---|---|
[책 정리]Modern Java In Action 1장 (0) | 2020.05.18 |
[책 정리]Modern Java In Action 6장 (0) | 2020.05.18 |
[책 정리]Modern Java In Action 시작 (0) | 2020.05.18 |
[책 정리]Modern Java In Action 4장 (0) | 2020.05.18 |