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에서 사용할 수 있습니다 .