3장 문자열, 비교기, 필터
JDK에서 도입한 일부 방법은 기능적 스타일 코드를 작성하는 데 매우 유용합니다. 우리는 이미 String과 같은 JDK 라이브러리의 일부 클래스와 인터페이스에 매우 익숙합니다. 익숙한 기존 스타일을 없애기 위해서는 이러한 새로운 메서드를 사용할 기회를 적극적으로 찾아야 합니다. 마찬가지로, 메서드가 하나만 있는 익명 내부 클래스를 사용해야 하는 경우 이제 이전처럼 번거롭게 작성할 필요 없이 이를 람다 식으로 바꿀 수 있습니다.
이 장에서는 람다 식과 메서드 참조를 사용하여 문자열을 탐색하고, Comparator 인터페이스를 구현하고, 디렉터리의 파일을 보고, 파일과 디렉터리의 변경 사항을 모니터링합니다. 이전 장에서 소개된 방법 중 일부는 이러한 작업을 더 잘 완료하는 데 도움이 되도록 여기에 계속 표시됩니다. 여러분이 배우는 새로운 기술은 길고 지루한 코드를 간결하고, 빠르게 구현하고, 유지 관리하기 쉬운 코드로 바꾸는 데 도움이 될 것입니다.
문자열 반복
chars() 메서드는 CharSequence 인터페이스의 일부인 String 클래스의 새로운 메서드입니다. String의 문자 시퀀스를 빠르게 탐색하려는 경우 매우 유용한 도구입니다. 이 내부 반복자를 사용하면 문자열의 각 문자에 대해 편리하게 작업할 수 있습니다. 먼저 문자열을 처리하는 데 사용해 보세요. 메소드 참조를 사용하는 몇 가지 방법은 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
최종 문자열 str = "w00t";
str.chars()
.forEach(ch -> System.out.println(ch));
chars() 메소드는 내부 반복자 forEach()를 사용하여 탐색할 수 있는 Stream 객체를 반환합니다. 반복자에서는 문자열의 문자에 직접 액세스할 수 있습니다. 아래는 문자열을 반복하고 각 문자를 인쇄한 결과입니다.
다음과 같이 코드 코드를 복사합니다.
119
48
48
116
이는 우리가 원하는 결과가 아닙니다. 문자가 보일 것으로 예상하지만 출력은 숫자입니다. 이는 chars() 메서드가 문자 유형 대신 정수 Stream을 반환하기 때문입니다. 먼저 이 API를 이해한 다음 출력 결과를 최적화해 보겠습니다.
이전 코드에서는 forEach 메서드의 입력 매개 변수로 람다 식을 만들었습니다. 단순히 매개변수를 println() 메소드에 전달합니다. 이 작업은 매우 일반적이므로 Java 컴파일러를 사용하여 이 코드를 단순화할 수 있습니다. 25페이지의 메소드 참조를 사용하는 것과 마찬가지로 이를 메소드 참조로 대체하고 컴파일러가 매개변수 라우팅을 수행하도록 하십시오.
인스턴스 메서드에 대한 메서드 참조를 만드는 방법을 살펴보았습니다. 예를 들어 name.toUpperCase() 메서드의 경우 메서드 참조는 String::toUpperCase입니다. 다음 예에서는 System.out을 정적으로 참조하는 인스턴스 메서드를 호출합니다. 메서드에서 참조하는 두 개의 콜론 중 왼쪽은 클래스 이름이나 표현식일 수 있습니다. 이러한 유연성을 통해 아래와 같이 println() 메서드에 대한 참조를 쉽게 만들 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
str.chars()
.forEach(System.out::println);
보시다시피 Java 컴파일러는 매개변수 라우팅을 매우 스마트하게 완료할 수 있습니다. 람다 표현식과 메소드 참조는 기능적 인터페이스가 수신되는 위치에만 나타날 수 있으며 Java 컴파일러는 거기에서 해당 메소드를 생성합니다(주석: 컴파일러는 메소드가 하나만 있는 기능적 인터페이스의 구현을 생성합니다.). 이전에 사용한 메소드는 String::toUpperCase를 참조하며 생성된 메소드에 전달된 매개변수는 결국 다음과 같이 메소드 호출의 대상 객체가 됩니다. 이는 메소드 참조가 클래스 이름(String)을 기반으로 하기 때문입니다. 위 예제의 메서드 참조는 PrintStream의 인스턴스이고 System.out을 통해 참조되는 식을 기반으로 합니다. 메소드 호출의 객체가 이미 존재하므로 Java 컴파일러는 생성된 메소드의 매개변수를 이 println 메소드(System.out.println(name))의 매개변수로 사용하기로 결정합니다.
(주석: 실제로는 주로 두 가지 시나리오가 있습니다. 메소드 참조도 전달됩니다. 하나는 탐색된 객체(물론 name.toUpperCase와 같은 메소드 호출의 대상 객체)이고 다른 하나는 System.out.println(name)과 같은 메소드 호출)
메소드 참조를 사용하면 코드가 훨씬 간단해집니다. 하지만 작동 방식을 더 깊이 이해할 필요가 있습니다. 메소드 참조에 익숙해지면 매개변수 라우팅을 스스로 알아낼 수 있습니다.
이 예제의 코드는 충분히 간결하지만 출력은 여전히 만족스럽지 않습니다. 문자가 보일 것으로 예상했지만 대신 숫자가 나타났습니다. 이 문제를 해결하기 위해 int를 문자로 출력하는 메소드를 작성해 보겠습니다.
다음과 같이 코드 코드를 복사합니다.
개인 정적 무효 printChar(int aChar) {
System.out.println((char)(aChar));
}
메소드 참조를 사용하면 출력 결과를 쉽게 최적화할 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
str.chars()
.forEach(IterateString::printChar);
이제 chars()가 반환한 결과는 int이지만 인쇄해야 할 때는 이를 문자로 변환합니다. 이번에는 최종적으로 문자가 출력됩니다.
다음과 같이 코드 코드를 복사합니다.
승
0
0
티
처음부터 int 대신 문자를 처리하려면 chars를 호출한 후 int를 문자로 직접 변환하면 됩니다.
다음과 같이 코드 코드를 복사합니다.
str.chars()
.mapToObj(ch -> Character.valueOf((char)ch))
.forEach(System.out::println);
여기서는 chars()가 반환한 Stream의 내부 반복자를 사용합니다. 물론 이 메서드보다 더 많은 것을 사용할 수 있습니다. Stream 객체를 얻은 후 map(), filter(), Reduce() 등과 같은 해당 메서드를 사용할 수 있습니다. filter() 메서드를 사용하여 숫자인 문자를 필터링할 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
str.chars()
.filter(ch -> Character.isDigit(ch))
.forEach(ch -> printChar(ch));
이런 식으로 출력할 때는 숫자만 볼 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
0
0
마찬가지로 람다 식을 filter() 및 forEach() 메서드에 전달하는 것 외에도 메서드 참조를 사용할 수도 있습니다.
다음과 같이 코드 코드를 복사합니다.
str.chars()
.filter(문자::isDigit)
.forEach(IterateString::printChar);
여기서 메서드 참조는 중복 매개변수 라우팅을 제거합니다. 이 예에서는 이전 두 가지 방법과 다른 사용법도 볼 수 있습니다. 처음으로 인스턴스 메서드를 참조했을 때는 정적 참조(System.out) 메서드였습니다. 이번에는 정적 메서드에 대한 참조입니다. 메서드 참조는 자동으로 지불되었습니다.
인스턴스 메서드와 정적 메서드에 대한 참조는 모두 동일해 보입니다(예: String::toUpperCase 및 Character::isDigit). 컴파일러는 메서드가 인스턴스 메서드인지 정적 메서드인지 확인하여 매개 변수를 라우팅하는 방법을 결정합니다. 인스턴스 메소드인 경우 생성된 메소드의 입력 매개변수를 메소드 호출의 대상 객체로 사용합니다(예: 매개변수 toUpperCase()). (물론 메소드 호출의 대상 객체와 같은 예외도 있습니다. System::out.println ())과 같이 지정되었습니다. 또한, 정적 메소드인 경우 생성된 메소드의 입력 매개변수는 Character.isDigit(매개변수)와 같이 참조되는 메소드의 매개변수로 사용됩니다. 152페이지의 부록 2에는 메소드 참조 및 해당 구문을 사용하는 방법에 대한 자세한 지침이 있습니다.
메소드 참조는 사용하기 편리하지만 여전히 문제가 있습니다. 메소드 이름 충돌로 인한 모호함입니다. 일치하는 메서드가 인스턴스 메서드이자 정적 메서드인 경우 컴파일러는 메서드 모호함으로 인해 오류를 보고합니다. 예를 들어 Double::toString을 이와 같이 작성하면 실제로 double 유형을 문자열로 변환하려고 하지만 컴파일러는 public String toString()의 인스턴스 메서드를 호출할지 아니면 public static String을 호출할지 알 수 없습니다. toString.(double) 메소드, 두 메소드 모두 Double 클래스이기 때문입니다. 그러한 상황이 발생하더라도 낙심하지 말고 람다 식을 사용하여 완료하세요.
함수형 프로그래밍에 익숙해지면 람다 표현식과 메서드 참조 사이를 마음대로 전환할 수 있습니다.
이 섹션에서는 Java 8의 새로운 메서드를 사용하여 문자열을 반복합니다. Comparator 인터페이스의 개선 사항을 살펴보겠습니다.