1. 개요

이 기사에서는 Java Streams 를 필터링하는 다양한 방법을 비교 합니다. 처음에는 어떤 솔루션이 더 읽기 쉬운 코드로 연결되는지 확인할 것입니다. 그런 다음 성능 관점에서 솔루션을 비교합니다.

2. 가독성

먼저 가독성 관점에서 두 가지 솔루션을 비교해 보겠습니다. 이 섹션의 코드 예제에서는 Student 클래스를 사용합니다.

public class Student {

    private String name;
    private int year;
    private List<Integer> marks;
    private Profile profile;

    // constructor getters and setters

}

우리의 목표는 다음 세 가지 규칙에 따라 학생 스트림을 필터링하는 것입니다 .

  • 프로필 은 Profile.PHYSICS 여야 합니다 .
  • 마크 의 개수는 3보다 커야 합니다.
  • 평균 점수 는 50보다 커야 합니다.

2.1. 다중 필터

Stream API를  사용 하면 여러 필터를 연결할 수 있습니다. 이를 활용하여 설명된 복잡한 필터링 기준을 충족할 수 있습니다. 게다가 조건을 부정 하려면 not 술어를 사용할 수 있습니다.

이 접근 방식은 깨끗하고 이해하기 쉬운 코드로 이어집니다.

@Test
public void whenUsingMultipleFilters_dataShouldBeFiltered() {
    List<Student> filteredStream = students.stream()
      .filter(s -> s.getMarksAverage() > 50)
      .filter(s -> s.getMarks().size() > 3)
      .filter(not(s -> s.getProfile() == Student.Profile.PHYSICS))
      .collect(Collectors.toList());

    assertThat(filteredStream).containsExactly(mathStudent);
}

2.2. 복잡한 조건의 단일 필터

대안은 더 복잡한 조건의 단일 필터를 사용하는 것입니다.

불행히도 결과 코드는 읽기가 조금 더 어려울 것입니다.

@Test
public void whenUsingSingleComplexFilter_dataShouldBeFiltered() {
    List<Student> filteredStream = students.stream()
      .filter(s -> s.getMarksAverage() > 50 
        && s.getMarks().size() > 3 
        && s.getProfile() != Student.Profile.PHYSICS)
      .collect(Collectors.toList());

    assertThat(filteredStream).containsExactly(mathStudent);
}

그러나 몇 가지 조건을 별도의 방법으로 추출하여 더 좋게 만들 수 있습니다.

public boolean isEligibleForScholarship() {
    return getMarksAverage() > 50
      && marks.size() > 3
      && profile != Profile.PHYSICS;
}

결과적으로 복잡한 조건을 숨기고 필터링 기준에 더 많은 의미를 부여합니다.

@Test
public void whenUsingSingleComplexFilterExtracted_dataShouldBeFiltered() {
    List<Student> filteredStream = students.stream()
        .filter(Student::isEligibleForScholarship)
        .collect(Collectors.toList());

    assertThat(filteredStream).containsExactly(mathStudent);
}

이것은 특히 모델 내부에 필터 논리를 캡슐화할 수 있는 경우 좋은 솔루션이 될 것입니다. 

3. 성능

여러 필터를 사용하면 코드의 가독성이 향상될 수 있습니다. 반면에 이것은 여러 개체의 생성을 의미하며 성능 손실로 이어질 수 있습니다. 이를 시연하기 위해 다양한 크기의 스트림 을 필터링하고 해당 요소에 대해 여러 검사를 수행합니다.

그런 다음 총 처리 시간을 밀리초 단위로 계산하고 두 솔루션을 비교합니다. 또한 테스트에 Parallel Streams 와 단순하고 오래된 for 루프를 포함할 것입니다.

 

스트림 파일러 크기 비교결과적으로 복잡한 조건을 사용하면 성능이 향상된다는 것을 알 수 있습니다. 

그러나 작은 샘플 크기의 경우 차이가 눈에 띄지 않을 수 있습니다.

4. 조건의 순서

단일 또는 다중 필터를 사용하는지 여부에 관계없이 검사가 최적의 순서로 실행되지 않으면 필터링으로 인해 성능이 저하될 수 있습니다.

4.1. 많은 요소를 필터링하는 조건

100개의 정수 스트림이 있고 20보다 작은 짝수를 찾고 싶다고 가정해 봅시다.

숫자의 패리티를 먼저 확인하면 총 150번의 확인이 완료됩니다. 첫 번째 조건은 매번 평가되고 두 ​​번째 조건은 짝수에 대해서만 평가되기 때문입니다.

@Test
public void givenWrongFilterOrder_whenUsingMultipleFilters_shouldEvaluateManyConditions() {
    long filteredStreamSize = IntStream.range(0, 100).boxed()
      .filter(this::isEvenNumber)
      .filter(this::isSmallerThanTwenty)
      .count();

    assertThat(filteredStreamSize).isEqualTo(10);
    assertThat(numberOfOperations).hasValue(150);
}

반면에 필터 순서를 반대로 하면 스트림을 적절하게 필터링하기 위해 총 120개의 검사만 필요합니다. 따라서 대부분의 요소를 필터링하는 조건을 먼저 평가해야 합니다.

4.2. 느리거나 무거운 조건

일부 조건은 잠재적으로 느릴 수 있습니다. 예를 들어, 필터 중 하나가 네트워크를 통한 외부 호출이나 무거운 논리를 실행해야 하는 경우입니다. 더 나은 성능을 위해 가능한 한 적은 횟수로 이러한 조건을 평가하려고 합니다. 따라서 다른 모든 조건이 충족되는 경우에만 평가하려고 합니다 .

5. 결론

이 기사에서는 Java 스트림 을 필터링하는 다양한 방법을 분석 했습니다. 먼저 가독성 관점에서 두 접근 방식을 비교했습니다. 여러 필터가 더 이해하기 쉬운 필터링 조건을 제공한다는 사실을 발견했습니다.

그런 다음 성능 관점에서 솔루션을 비교했습니다. 복잡한 조건을 사용하여 더 적은 수의 개체를 생성하면 전체 성능이 향상된다는 것을 배웠습니다.

항상 그렇듯이 소스 코드는  GitHub에서 사용할 수 있습니다 .

Generic footer banner