요소 찾기
이제 우리는 컬렉션을 변환하는 우아하게 설계된 방법에 익숙하지만 요소를 찾는 데는 쓸모가 없습니다. 하지만 이를 위해 필터 방식이 탄생했습니다.
이제 이름 목록에서 N으로 시작하는 이름을 제거하려고 합니다. 물론 아무 것도 없을 수도 있고 결과는 빈 집합일 수도 있습니다. 먼저 기존 방법을 사용하여 구현해 보겠습니다.
다음과 같이 코드 코드를 복사합니다.
final List<String> startWithN = new ArrayList<String>();
for(문자열 이름 : 친구) {
if(name.startsWith("N")) {
startWithN.add(이름);
}
}
이렇게 간단한 이벤트에 너무 많은 코드를 작성하는 것은 꽤 장황한 일입니다. 먼저 변수를 만든 다음 이를 빈 컬렉션으로 초기화합니다. 그런 다음 원본 컬렉션을 반복하여 지정된 문자로 시작하는 이름을 찾습니다. 발견되면 컬렉션에 삽입됩니다.
필터 메소드를 사용하여 위의 코드를 재구성하여 이것이 얼마나 강력한지 살펴보겠습니다.
다음과 같이 코드 코드를 복사합니다.
최종 목록<문자열> startWithN =
친구.스트림()
.filter(이름 -> name.startsWith("N"))
.collect(Collectors.toList());
필터 메서드는 부울 값을 반환하는 람다 식을 받습니다. 표현식이 true로 평가되면 실행 컨텍스트의 해당 요소가 결과 세트에 추가되고, 그렇지 않으면 건너뜁니다. 최종적으로 반환되는 것은 표현식이 true를 반환하는 요소만 포함하는 Steam입니다. 마지막으로 Collect 메소드를 사용하여 컬렉션을 목록으로 변환합니다. 이 메소드에 대해서는 52페이지의 Collect 메소드 및 Collecters 클래스 사용에서 자세히 설명합니다.
이 결과 집합의 요소를 인쇄해 보겠습니다.
다음과 같이 코드 코드를 복사합니다.
System.out.println(String.format("%d개의 이름을 찾았습니다", startWithN.size()));
출력을 보면 이 메서드가 컬렉션에서 일치하는 모든 요소를 찾았다는 것이 분명합니다.
다음과 같이 코드 코드를 복사합니다.
2개의 이름을 찾았습니다.
map 메소드와 마찬가지로 filter 메소드도 반복자를 반환하지만 그게 전부입니다. 맵에서 반환된 컬렉션은 입력 컬렉션과 크기가 동일하지만 어떤 필터가 반환되는지 말하기는 어렵습니다. 반환되는 집합의 크기 범위(0부터 입력 집합의 요소 수까지)입니다. 맵과 달리 필터는 입력 세트의 하위 세트를 반환합니다.
지금까지는 람다 표현식이 가져온 코드 단순성에 매우 만족했지만, 주의하지 않으면 코드 중복 문제가 서서히 커지기 시작할 것입니다. 아래에서 이 문제를 논의해 보겠습니다.
람다 표현식 재사용
람다 표현식은 매우 간결해 보이지만 실제로는 주의하지 않으면 코드를 중복하게 만들기 쉽습니다. 중복으로 인해 코드 품질이 낮아지고 유지 관리가 어려워집니다. 변경하려면 여러 관련 코드를 함께 변경해야 합니다.
중복을 피하는 것도 성능 향상에 도움이 될 수 있습니다. 관련 코드가 한곳에 집중되어 있어 성능을 분석한 후 여기서 코드를 최적화할 수 있어 코드의 성능을 쉽게 향상시킬 수 있습니다.
이제 람다 식을 사용하면 쉽게 코드 중복이 발생할 수 있는 이유를 살펴보고 이를 방지하는 방법을 고려해 보겠습니다.
다음과 같이 코드 코드를 복사합니다.
최종 목록<String> 친구 =
Arrays.asList("브라이언", "네이트", "닐", "라주", "사라", "스콧");
최종 List<String> 편집자 =
Arrays.asList("브라이언", "재키", "존", "마이크");
최종 목록<String> 동지 =
Arrays.asList("케이트", "켄", "닉", "폴라", "잭");
특정 문자로 시작하는 이름을 필터링하고 싶습니다.
특정 문자로 시작하는 이름을 필터링하고 싶습니다. 먼저 필터 메소드를 사용하여 간단히 구현해 보겠습니다.
다음과 같이 코드 코드를 복사합니다.
최종 장기 카운트FriendsStartN =
친구.스트림()
.filter(이름 -> name.startsWith("N")).count();
최종 긴 카운트EditorsStartN =
editors.stream()
.filter(이름 -> name.startsWith("N")).count();
최종 긴 카운트ComradesStartN =
동지.스트림()
.filter(이름 -> name.startsWith("N")).count();
람다 표현식을 사용하면 코드가 간결해 보이지만 자신도 모르게 코드가 중복됩니다. 위의 예에서 람다 식을 변경하려면 두 곳 이상을 변경해야 하지만 이는 불가능합니다. 다행히도 람다 표현식을 변수에 할당하고 객체처럼 재사용할 수 있습니다.
람다 표현식의 수신자인 필터 메소드는 java.util.function.Predicate 기능 인터페이스에 대한 참조를 수신합니다. 여기서 Java 컴파일러는 지정된 람다 표현식을 사용하여 Predicate의 테스트 메서드 구현을 생성하는 데 다시 유용합니다. 이제 매개변수가 정의된 위치에서 메소드를 생성하는 대신 Java 컴파일러에 이 메소드를 생성하도록 보다 명시적으로 요청할 수 있습니다. 위의 예에서는 람다 식을 Predicate 유형의 참조에 명시적으로 저장한 다음 이 참조를 필터 메서드에 전달할 수 있습니다. 이렇게 하면 코드 중복을 쉽게 피할 수 있습니다.
이전 코드를 리팩토링하여 DRY 원칙을 준수하도록 하겠습니다. (Don't Repeat Yoursef - DRY - 원칙은 The Pragmatic Programmer: From Journeyman to Master [HT00] 책을 참조하십시오.)
다음과 같이 코드 코드를 복사합니다.
final Predicate<String> startWithN = 이름 -> name.startsWith("N");
최종 장기 카운트FriendsStartN =
친구.스트림()
.filter(startsWithN)
.세다();
최종 긴 카운트EditorsStartN =
editors.stream()
.filter(startsWithN)
.세다();
최종 긴 카운트ComradesStartN =
동지.스트림()
.filter(startsWithN)
.세다();
이제 람다 식을 다시 작성하는 대신 한 번 작성하고 startWithN이라는 Predicate 유형의 참조에 저장합니다. 다음 세 가지 필터 호출에서 Java 컴파일러는 Predicate로 위장한 람다 표현식을 보고 웃으며 조용히 수락했습니다.
새로 도입된 이 변수는 코드 중복을 제거합니다. 그러나 불행하게도 나중에 살펴보겠지만, 적군이 곧 돌아와서 우리를 위해 그들을 파괴할 수 있는 더 강력한 무기가 있는지 살펴보겠습니다.