Java Stream 활용하기

푸드테크·2022년 9월 5일
0

안녕하세요 푸드테크팀 백엔드 개발자 박형민 입니다

이번 포스팅에서는 저번 Stream 소개 글에 이에 가이드글을 작성해보고자 합니다.




1. 필터링

  • 스트림에서 요소를 선택하는 필터링을 제공합니다.
  • 필터링에는 프리디케이트 필터링 방법과 고유 요소만 필터링하는 방법이 있습니다.

1)  Predicate 필터링

  • 스트림 인터페이스는 filter 메소드를 지원하는데, 이 filter 메소드는 predicate 함수를(Boolean 타입 함수) 를 인수로 받아서 프리디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환합니다.
 

List<Dish> vegetarianMenu = menu.stream()
				.filter(Dish::isVegetarian)
                                .collect(toList());

2) 고유 요소 필터링 (Distinct)

  • 스트림은 중복된 요소를 제거하고 고유 요소로 이루어진 스트림을 반환하는 distinct 메소드도 지원합니다.

  • 고유 여부는 스트림에서 만든 객체의 hashCode, equals 로 결정합니다.

List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,123,41,5,1,2,3,4,5,5,6,7,8)
        .stream()
        .filter(number->number%2==0)
        .distinct()
        .collect(Collectors.toList());
 



2. 스트림 슬라이싱

  • 스트림 슬라이싱은, 스트림의 요소를 선택하거나 스킵하는 것을 말합니다.

1) Predicate를 이용한 슬라이싱

자바 9 이상 부터는 takeWile 메소드와 dropWhile 메소드를 제공합니다.



📌 TAKEWILE

  • takeWile 메소드는, predicate를 만족하지 않는  첫 번째 지점부터 마지막 스트림을 버립니다.

  • takeWhile 메소드는 조건에 맞지않는 그 순간 Stream 조회를 종료하기 때문에 정렬되어있는 Stream 에 매우 유용합니다.


// 전체 리스트 순회
List<Dish> filterMenu = specialMenu.stream()
                            .filter(dish -> dish.getCalories() < 320)
                            .collect(toList());
        
// 조건에 맞지 않으면 Stop
List<Dish> filterMenu = specialMenu.stream()
                            .takeWhile(dish -> dish.getCalories() < 320)
                            .collect(toList());



📌 DROPWHILE

  • dropWhile메소드는, takeWhile 과 정반대의 결과를 추출합니다.

  • 첫 번째 요소부터, predicate를 처음으로 만족하는 지점까지 버리고 나머지를 반환합니다.

  • dropWhile 은 프리디케이트가 처음으로 거짓이 되는 지점까지 발견된 요소를 버립니다.

  • 프리디케이트가 거짓이되면, 그 즉시 작업을 중단하고 남은 모든 요소를 반환합니다.


// 처음으로 거짓이 되는 지점까지 버림.
List<Dish> filterMenu = specialMenu.stream()
                            .dropWhile(dish -> dish.getCalories() < 320)
                            .collect(toList());


2) 스트림 축소

  • limit(n) : 주어진 값 이하의 크기를 가지는 새로운 스트림을 반환하는 스트림 메소드입니다.

  • limit 은 조건에 맞는 요소가 채워지면 그 즉시 스트림을 반환합니다.

// 3개 요소 반환
List<Dish> filterMenu = specialMenu.stream()
                            .filter(dish -> dish.getCalories() < 320)
                            .limit(3)
                            .collect(toList());

3) 스트림 요소 건너띄기

  • Skip(n) : 스트림은 처음  n 개의 요소를 제외한(스킵한) 스트림을 반환하는 메소드를 지원합니다.
  • limit(n) 과 정반대의 결과를 도출합니다.
  • n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환됩니다.
  // 처음 3개 요소 건너뛰기
  List<Dish> filterMenu = specialMenu.stream()
                            .filter(dish -> dish.getCalories() < 320)
                            .skip(3)
                            .collect(toList());


3. 스트림 매핑

  • 특정 객체에서 특정 데이터를 선택하는 작업은 데이터 처리 과정에서 자주 수행되는 연산입니다.
  • Stream 에서는 Map 과  FlatMap 메소드로 특정 데이터를 선택할 수 있는 기능을 제공합니다.

1) Stream Map

  • 스트림은 함수를 인수로 받는 map 메소드를 지원합니다.
  • 인수로 제공된  함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑됩니다.
  • 이 과정은 요소를 고친다는 개념보다, 새로운 버전을 만든다라는 개념에서 Trans(변환) 보다 mapping(매핑) 으로 지어졌다고합니다.
    // 요리명을 추출하는 예제
    List<String> dishNames = menu.stream()
                            .map(Dish::getName)
                            .collect(toList());
                            
                            
    // 단어 리스트의 글자수 추출
    List<String> words = Arrays.asList("Modern", "Java", "In", "Action");
    List<Integer> wordLengths = words.stream()
          .map(String::length)
          .collect(toList());


    // map 을 연결하여 요리명의 글자수 추출
    List<String> dishNames = menu.stream()
                            .map(Dish::getName)
                            .map(String::length)
                            .collect(toList());

2) Stream FlatMap (스트림 평면화)

  • flatMap 메서드는 스트림의 각 값을 다른 스트림으로 만든 다음 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행합니다.
  • 예를들어, list 혹은 배열의 요소들을 각각의 스트림으로 만든 후, 이 만들어진 모든 스트림 요소를 하나의 스트림으로 만들어 어떤 작업을 진행하고 싶을때 flatMap을 사용합니다.

Stream flatmap 예시

  • 예를들어 [“hello”, “world”] 리스트가 [“h”, “e”, “l”, “l”, “o”, “w”, “o”, “r”, “l”, “d”] 가 되도록 변경
  List<String> uniqueCharacters = words.stream()
          .map(word -> word.split("")) // 각 단어를 개별 문자를 포함하는 배열로 변환 
          .flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화
          .distinct()
          .collect(toList());
  • flatMap은 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑합니다.
  • 📌 즉, map(Arrays::stream)과 달리 flatMap은 하나의 평면화된 스트림을 반환합니다.



4. 스트림 검색과 매칭

  • 특정 속성이 데이터 집합에 있는지 여부, 즉 검색 처리도 스트림 API에서 제공합니다.
  • ex) allMatch, anyMatch, noneMatch, findFirst, findAny



📌 allMatch

  • Predicate가 모든 요소와 일치하는지 검사
boolean isMatch = menu.stream().allMatch(Dish -> dish.getCalories() < 1000)

📌 anyMatch

  • Predicate가 적어도 한 요소와 일치 하는지 확인
boolean isMatch = menu.stream().anyMatch(Dish -> dish.getCalories() < 1000)

📌 NoneMatch

  • noneMatch는 allMatch와 반대 연산을 수행합니다.
  • 즉. noneMatch는 주어진 predicate와 일치하는 요소가 없는지 확인한다.
boolean isMatch = menu.stream().noneMatch(Dish -> dish.getCalories() < 1000)

📌 요소검색 (findAny, findFirst)

findAny

  • findAny 메서드는 현재 스트림에서 임의의 요소를 반환합니다.
  • findAny 메서드를 다른 스트림 연산과 연결해서 사용할 수 있습니다.
  // filter 와 findAny를 활용해 채식 요리를 선택하는 방법
  Optional<Dish> dish = words.stream()
          .filter(Dish::isVegetarian)
          .findAny();	//findAny는 Optional 객체를 반환

findFirst

  • 첫 번째 요소 찾기
  • 리스트 또는 정렬된 데이터로 부터 생성된 순서가 정해진 스트림에서 첫번째 요소를 찾기 위한 방법입니다.
//  3으로 나누어 떨어지는 첫 번째 제곱근 값을 반환하는 코드.
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> first = someNumbers.stream()
        .map(n -> n * n)
        .filter(n -> n % 3 == 0)
        .findFirst();



 

5. 리듀싱

  • Stream API 는 마치 Sql 처럼 모든 스트림 요소의 값을 처리해서 결과를 도출하는 Reduce 메소드를 제공합니다.
  • reduce 메소드의 연산 과정은, 각 요소의 결과에 반복적으로 더해집니다,
  • 그렇기 때문에 reduce를 사용하면 반복된 패턴을 추상화 할 수 있습니다.
//초깃값 0.
// 두 요소를 조합해 새로운 값을 만드는 BinaryOperator. 예제에서는 람다 표현식 (a, b) -> a + b를 사용했다.
//reduce로 다른 람다, 즉 (a, b) -> a * b를 넘겨주면 모든 요소에 곱셈을 적용할 수 있다.
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

Java 8 에서는 Integer 클래스에 두 숫자를 더하는 정적 sum 메서드를 지원하여 아래와 같이 사용할 수 있습니다.

int sum = numbers.stream().reduce(0, Integer::sum);

👏 초기값을 없앨수도 있습니다.

  • 초깃값을 받지 않도록 오버로드된 reduce는 결과값으로 Optional객체를 반환합니다.
Optional sum = numbers.stream().reduce((a, b) -> a + b);

👏 Stream 최대 값, 최솟 값

  • 최대값과 최솟값을 찾는 연산도 reduce를 활용하면 쉽게 할 수 있습니다.
Optional max = numbers.stream().reduce(Integer::max);
Optional min = numbers.stream().reduce(Integer::min);



6. 레듀싱 응용

1) 기본형 특화 레듀싱

ex) 객체로 변환하고 reduce를 사용해서 sum을 하는 과정
  int calories = menu.stream()
                      .map(Dish::getCalories)
                      .reduce(0, Integer::sum);

Stream에서는 기본형 특화 스트림을 제공해서 아래처럼 조금 더 명시적이고 간편하게 기본형 특화 메소드들을 사용할 수 있도록 합니다.

int calories = menu.stream()
                    .mapToInt(Dish::getCalories)
                    .sum();
  • ex) mapToInt, mapToDouble, mapToLong

2) OptionalInt

  • 기본값이 없는 Stream 연산에서 최대 최솟값을 구할 떄, 이를 구별하기위해 기본형 특화 스트림 + Optional 기능을 제공합니다.

    OptionalInt maxCalories = menu.stream()
                        .mapToInt(Dish::getCalories)
                        .max();
    

  
물론 Int 형 뿐만아니라 다른 기본형 특화 스트림 타입에도 적용이 가능합니다.

  
  
  
  감사합니다!!
profile
푸드 테크 기술 블로그

1개의 댓글

comment-user-thumbnail
2022년 9월 7일

takeWile 메소드와 dropWhile 는 모르는 함수였는데!! 앞으로 요긴하게 쓸수 있을거 같습니다!

답글 달기