본문 바로가기
야미스터디/Java

[Java - Stream] 03. filtering

by 의정부핵꿀밤 2022. 12. 7.
728x90

Intro

데이터를 처리하기 위해 조건문이 있다면, 스트림에서는 동일한 기능으로 필터링을 제공한다.

 

 

필터링

스트림의 요소를 선택하는 방법으로 Predicate 필터링, 고유 요소 필터링, 스트림의 일부 요소를 무시하거나 스트림을 주어진 크기로 축소하는 방법 등이 있다.

 

1) Predicate로 필터링

스트림 인터페이스는 filter 메서드를 지원하고, Predicate(boolean을 반환하는 함수)를 인수로 받아서 일치하는 모든 요소를 포함하는 스트림을 반환한다.

Predicate는 자바 8에 나온 함수형 인터페이스로 boolean 반환 타입의 test 메서드가 존재한다.

public class ChickenFilter {  
   private final List<Chicken> chickens;  
   public ChickenFilter(List<Chicken> chickens) {  
      this.chickens = chickens;  
   }  
   public List<Chicken> filterBrandChicken(ChickenBrand brand) {  
      return this.chickens.stream()  
         .filter(chicken -> chicken.isMatch(brand)) // 브랜드에 매핑되는 치킨으로 필터링
         .collect(Collectors.toList());  
   }  
}

 

 

ChickenFilter 클래스에서 제공하는 filterBrandChicken을 사용하여 특정 브랜드의 치킨을 필터리아여 조회하는 테스트

class ChickenFilterTest {  
   @DisplayName("브랜드로 필터링 테스트")  
   @Test  
   void testCase1() {  
      List<Chicken> originChickenList = List.of(  
         new Chicken(KFC, 10_000),  
         new Chicken(KFC, 12_000),  
         new Chicken(GCOVA, 9_000),  
         new Chicken(ChickenBrand.BBQ, 15_000)  
      );  
      ChickenFilter chickenFilter = new ChickenFilter(originChickenList);  
      List<Chicken> kfcChicken = chickenFilter.filterBrandChicken(KFC);  
      assertAll(  
         () -> assertThat(kfcChicken).hasSize(2),  
         () -> assertThat(kfcChicken.get(0).getBrand()).isEqualTo(KFC),  
         () -> assertThat(kfcChicken.get(1).getBrand()).isEqualTo(KFC)  
      );  
   }
}

 

2) 스트림 축소(limit)

스트림은 주어진 사이즈 이하의 크기를 갖는 새로운 스트림을 반환하는 limit 메서드를 지원한다.

스트림이 정렬되어 있으면 최대 n개의 요소를 반환할 수 있다.

public class ChickenDistinct {  
   private final List<Chicken> chickens;  
   public ChickenDistinct(List<Chicken> chickens) {  
      this.chickens = chickens;  
   }
   public List<Chicken> distinctByBrandAndLimit(ChickenBrand brand, int limit) {  
      return chickens.stream()  
         .filter(chicken -> chicken.isMatch(brand))  
         .sorted(Comparator.comparing(Chicken::getPrice).reversed())  
         .limit(limit)  
         .collect(Collectors.toList());  
   }  
}

 

정렬되지 않은 스트림에도 limit를 사용할 수 있으나, 소스가 정렬되어 있지 않다면 limit의 결과도 정렬되지 않은 상태로 반환된다.

class ChickenDistinctTest { 
   @DisplayName("브랜드로 필터링 및 제한된 데이터 사이즈만 조회")  
   @Test  
   void testCase2() {  
      List<Chicken> originChickenList = List.of(  
         new Chicken(KFC, 10_000, "맛있는 치킨"),  
         new Chicken(KFC, 12_000, "더 맛있는 치킨"),  
         new Chicken(GCOVA, 9_000, "X코바 치킨"),  
         new Chicken(ChickenBrand.BBQ, 15_000, "BBC 치킨")  
      );  
      ChickenDistinct chickenDistinct = new ChickenDistinct(originChickenList);  
      List<Chicken> chickens = chickenDistinct.distinctByBrandAndLimit(ChickenBrand.KFC, 1);  
      assertAll(  
         () -> assertThat(chickens).hasSize(1),  
         () -> assertThat(chickens.get(0).getPrice()).isEqualTo(12_000)  
      );  
   }  
}

 

 


정리

  • filter, distinct, skip, limit 메서드로 스트림을 필터링하거나 자를 수 있다.
  • filter, map 등은 상태를 저장하지 않는 상태 없는 연산(stateless operation)이다.
  • reduce 같은 연산은 값을 계산하는 데 필요한 상태를 저장한다.
  • sorted, distinct 등의 메서드는 새로운 스트림을 반환하기에 앞서 스트림의 모든 요소를 버퍼에 저장해야 한다.
  • 이러한 메서드를 상태 있는 연산(stateful operation)이라 부른다.
💡 주의사항
- 직접 동일한 요구사항을 반복문으로 구현하고, 스트림으로 구현했을 때 어떤 점에서 좋은지 고민해봐야 한다.
- 성능에 대한 이야기를 아직 하지 않았지만, 스트림 API는 기본적으로 for-each 보다 동작하는 비용이 더 들고, 스트림으로 변경해서 오히려 가독성이 떨어질 수 있기 때문에 더욱 학습하고 활용해야 한다.
728x90

댓글