💡 메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 쓰고, 그렇지 않을 때는 람다를 사용하라
📌 메서드 참조(method reference)
List<Person> people = Arrays.asList(new Person("안OO"), new Person("조OO"),
new Person("양OO"), new Person("안OO"));
위 코드처럼 여러 사람이 있을 때, 이름을 key로 하는 사람 수 value를 나타내는 Map을 구해보자.
Map<String, Integer> counts = new HashMap<>();
for (Person person : people) {
counts.merge(person.getName(), 1, (existingValue, providedValue) -> existingValue + providedValue);
}
- 키가 맵 안에 있다면, 키와 숫자 1을 매핑한다. 이미 있다면 기존 매핑값을 증가시킨다.
- existingValue, providedValue는 명료하긴 하나, 하는 일 없이 공간을 꽤 많이 차지한다.
- 둘은 단순히 인수의 합을 반환할 뿐이다.
Map<String, Integer> counts = new HashMap<>();
for (Person person : people) {
counts.merge(person.getName(), 1, Integer::sum);
}
- Java 8부터 모든 기본 타입의 박싱 타입은 람다와 기능이 같은 정적 메서드 sum을 제공한다.
- 따라서 람다 대신 메서드 참조를 전달하면 같은 결과를 더 간결하게 얻을 수 있다.
- 메서드 참조는 기능을 잘 드러내는 이름을 짓고, 적절한 설명을 문서로 남길 수도 있다.
더보기
✒️ 점진적 리팩토링
"양"씨 성을 가진 이름만 모아보자.
1️⃣ 익명 클래스
List<Person> onlyYang = people.stream()
.filter(new Predicate<Person>() {
@Override public boolean test(Person person) {
return person.getName().startsWith("양");
}
})
.collect(toList());
2️⃣ 람다
List<Person> onlyYang = people.stream()
.filter(person -> person.getName().startsWith("양"))
.collect(toList());
3️⃣ 메서드 참조
List<Person> onlyYang = people.stream()
.filter(Person::isLastNameYang)
.collect(toList());
- 람다식을 분리하여 Person의 팩터리 메서드로 바꾸고, 메서드 참조 방식을 활용할 수 있다.
📌 람다가 더 나은 경우
class Service {
public void execute(Supplier<?> supplier) {
supplier.get();
}
}
class GoshThisClassNameIsHumongousAndImNotEvenKiddingItHasAnExtraLongName {
private void execute(Supplier<?> supplier) {
supplier.get();
}
public static Object action() {
return null;
}
public static void main(String[] args) {
Service service = new Service();
service.execute(GoshThisClassNameIsHumongousAndImNotEvenKiddingItHasAnExtraLongName::action);
service.execute(() -> action());
}
(ㅋㅋㅋㅋㅋㅋㅋㅋㅋ 너무 오버했나..)
- 어떤 람다는 매개변수 이름 자체가 좋은 가이드가 되기도 한다.
- 메서드와 람다가 같은 클래스 안에 있을 때도 람다가 나은 경우가 많다. (위의 예시)
- java.util.function 패키지의 Function.identity()보다 (x -> x)가 훨씬 간결하다.
📌 메서드 참조 5개 유형
메서드 참조 유형 | 예 | 람다 표현 | 설명 |
정적 | Integer::parseInt | str -> Integer.parseInt(str) | • 위의 예시같은 가장 흔한 예 |
한정적 (인스턴스) | Instant.now()::isAfter | Instant then = Instant.now(); t -> then.isAfter(t) |
• 수신 객체(receiving object; 참조 대상 인스턴스)를 특정 • 근본적으로 정적 참조와 비슷 • 함수 객체가 받는 인수 == 참조되는 메서드가 받는 인수 |
비한정적 (인스턴스) | String::toLowerCase | str -> str.toLowerCase() | • 수신 객체를 특정하지 않음 • 함수 객체를 사용하는 시점에 수신 객체를 알려준다. • 주로 스트림 파이프라인에서 매핑과 필터 함수에 쓰인다. (Item 45) |
클래스 생성자 | TreeMap<K,V>::new | () -> new TreeMap<K, V>() | • 팩터리 객체로 사용 |
배열 생성자 | int[]::new | len -> new int[len] | • 팩터리 객체로 사용 |
📌 람다로는 불가능하나 메서드 참조로는 가능한 유일한 예. 제네릭 함수 타입
interface G1 {
<E extends Exception> Object m() throws E;
}
interface G2 {
<F extends Exception> String m() throws F;
}
interface G extends G1, G2 {
}
- 함수형 인터페이스 G를 함수 타입으로 표현 시, <F extends Exception> () -> String throws F
- 함수형 인터페이스를 위한 제네릭 함수 타입은 메서드 참조 표현식으로만 구현 가능