ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [책 정리]Modern Java In Action 5장
    책정리/Modern Java In Action 2020. 5. 18. 00:07

    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>>
    • 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
      }
    반응형

    댓글

Designed by Tistory.