티스토리 뷰

개념

  • Predicate<T>를 파라미터로 받아서 Predicatetrue인 모든 요소를 포함하는 스트림을 반환하는 메서드

예제

  • 아래 예제에서 사용되는 내용들은 다양한 상황을 연출하기 위해서 임의로 만들어진 내용이므로 특정 속성에 대해서 왜 저 타입이 사용되었는지 의문을 가지지 말자!

  • 예제 코드에서 사용되고 있는 스펙

    1. Java 15 preview (record라는 새로운 클래스 개념을 사용하기 위해서 해당 프리뷰 버전을 사용. 하위 버전의 경우는 일반 클래스를 생성한 후 getter를 만들고 사용하면 됨) - 참고
    2. JUnit 5
    3. Gradle 6.7
  • 소스 코드

기본 데이터 생성

public record Person(int no, String name, int age, Nation nation, int gender) {
}
public enum Nation {
    KOREA, JAPAN, CHINA, USA, UK
}
private final FilterUsage filterUsage = new FilterUsage();
private final List<Person> people = List.of(
        new Person(1, "hong", 15, Nation.CHINA, 1),
        new Person(2, "kim", 20, Nation.KOREA, 0),
        new Person(3, "miya", 18, Nation.JAPAN, 1),
        new Person(4, "hong", 30, Nation.USA, 0));

특정 나이 이상의 사람들 조회

  • 특정 나이 이상의 사람들을 필터링하는 메서드를 만들어보자.
public List<Person> getPeopleByAgeGreaterThanEqual(List<Person> people, int age) {
    return people.stream()
            .filter(p -> p.age() >= age)
            .collect(Collectors.toList());
}
  • 나이가 20세 이상인 사람을 조회해보자.
@Test
@DisplayName("특정 나이 이상의 사람들 조회")
void getPeopleByAgeGreaterThanEqualTest() {
    final var result = filterUsage.getPeopleByAgeGreaterThanEqual(people, 20);
    result.forEach(System.out::println);
    assertEquals(2, result.size());
    assertEquals("kim", result.get(0).name());
    assertEquals("hong", result.get(1).name());
}
  • 콘솔에 다음과 같은 실행 결과와 함께 테스트가 성공한다.

  • 이번에는 위에서 만든 getPeopleByAgeGreaterThanEqual 메서드를 사용해서 결과로 빈 리스트가 반환되는 즉, 필터를 통과한 데이터가 없는 경우에 대해서 알아보자
  • 나이가 40세 이상인 사람을 조회해보자.
@Test
@DisplayName("특정 나이 이상의 사람들 조회(필터 결과가 없음)")
void getPeopleByAgeGreaterThanEqualWithEmptyResultTest() {
    final var result = filterUsage.getPeopleByAgeGreaterThanEqual(people, 40);
    result.forEach(System.out::println);
    assertEquals(Collections.emptyList(), result);
}
  • 콘솔에 출력되는 결과는 없고 테스트는 성공한다.

특정 성별이 아닌 사람들 조회

  • 특정 성별이 아닌 사람들을 필터링하는 메서드를 만들어보자.
public List<Person> getPeopleByGenderNot(List<Person> people, int gender) {
    return people.stream()
            .filter(Predicate.not(p -> p.gender() == gender))
            .collect(Collectors.toList());
}
  • 위의 코드에서 Predicate.not()이라는 메서드가 사용되었는데, 다음의 시그니쳐를 가진 메서드이다.

  • Predicate 함수형 인터페이스의 static 메서드로 Predicate를 인자로 받아서 그 결과를 반전시키는 메서드이다.
  • 그 반환 결과는 Predicatefilter 메서드에 파라미터로 사용할 수 있다.
  • 위 메서드 설명을 읽어보면 메서드 바로 위에 Since: 11 를 통해 Java 11에 추가되어 상위 버전에서 사용될 수 있음을 알 수 있다.
  • Java 10 이하 버전을 사용하고 있다면 Predicate.not() 메서드를 사용할 수 없기 때문에 다음과 같이 작성하면 된다.
public List<Person> getPeopleByGenderNot(List<Person> people, int gender) {
    return people.stream()
            .filter(p -> p.gender() != gender)
            .collect(Collectors.toList());
}
  • 물론 Java 11 이상 버전을 사용하고 있어도 Predicate.not()을 사용하지 않고 위의 코드처럼 사용해도 무방하다.

    • 필자의 경우 코드의 선언적으로 명시해주는 스타일을 좋아해서 위의 Predicate.not() 방식을 선호한다.
    • 두 방식의 의미적 차이를 추가적으로 공부하거나 고민해보면 좋을 것 같다.
  • 성별이 '남자'가 아닌 사람을 조회해보자. (여기서 gender0이면 남자이다.)

@Test
@DisplayName("특정 성별이 아닌 사람들 조회")
void getPeopleByGenderNotTest() {
    // 0: Male, 1: Female
    final var result = filterUsage.getPeopleByGenderNot(people, 0);
    result.forEach(System.out::println);
    assertEquals(2, result.size());
    assertEquals("hong", result.get(0).name());
    assertEquals("miya", result.get(1).name());
}
  • 콘솔에 다음과 같은 실행 결과와 함께 테스트가 성공한다.

특정 나라에 살고 특정 나이 이상인 사람들 조회

  • 특정 나라에 살고 있고 특정 나이 이상인 사람들을 필터링하는 메서드를 만들어보자.
public List<Person> getPeopleByNationAndAgeGreaterThanEqual(List<Person> people, Nation nation, int age) {
    return people.stream()
            .filter(p -> p.nation() == nation)
            .filter(p -> p.age() >= age)
            .collect(Collectors.toList());
}
  • 다음과 같이 필터 조건이 여러 개일 경우 filter 메서드를 연결해서 사용할 수 있다.
  • 다음과 같이 하나의 필터로 사용할 수도 있다.
public List<Person> getPeopleByNationAndAgeGreaterThanEqual(List<Person> people, Nation nation, int age) {
    return people.stream()
            .filter(p -> p.nation() == nation && p.age() >= age)
            .collect(Collectors.toList());
}
  • 실행 결과는 동일하게 나타난다.

  • 위 두가지 방식에 대해서도 고민해보면 좋을 것 같다.

    • 필자는 필터에서 사용하는 필드가 다르다면 처음 방식처럼 분리하는 것이 좋다고 생각한다. 직관적이기 때문이다.
    • 성능적인 문제가 있는지 확인해보지는 않았지만 아시는 분이나 심심해서 성능 측정을 해보신 분이 계시다면 공유해주시면 감사합니다ㅎㅎ
  • 한국에 살고있고 나이가 15세 이상인 사람을 조회해보자.

@Test
@DisplayName("특정 나라에 살고 특정 나이 이상인 사람들 조회")
void getPeopleByNationAndAgeGreaterThanEqualTest() {
    final var result = filterUsage.getPeopleByNationAndAgeGreaterThanEqual(people, Nation.KOREA, 15);
    result.forEach(System.out::println);
    assertEquals(1, result.size());
    assertEquals("kim", result.get(0).name());
}
  • 콘솔에 다음과 같은 실행 결과와 함께 테스트가 성공한다.

이상 Stream API에서 사용하는 중간 연산 중 많이 사용되는 filter 메서드의 간단한 사용법에 대해 살펴보았다.

댓글