아이템45
// 이 코드는 키가 맵 안에 없다면 키와 숫자 1을 매핑, 이미 있다면 기존 매핑 값을 증가시킨다.
map.merge(key, 1, (count, incr) -> count + incr);이 코드는 Map에 추가된 merge 메서드를 사용했다. 람다식을 사용하여 맵에 {키, 함수의 결과} 쌍을 저장한다.
아쉬운 점은 매개변수인 count, incr은 크게 하는 일 없이 공간을 차지한다. 자바8이 되면서 Integer 클래스(박싱타입)는 람다와 기능이 같은 정적 메서드 sum을 제공하기 시작했다.
다음처럼 메서드 참조를 사용하면 더욱 간단해진다.
map.merge(key, 1, Integer::sum);매개변수의 수가 늘어날수록 메서드 참조로 제거할 수 있는 코드양도 늘어난다.
람다로 할 수 없는 일이라면 메서드 참조로도 할 수 없다.
람다로 작성할 코드를 새로운 메서드에 담은 다음, 람다 대신 그 메서드 참조를 사용하는 식이다.
메서드 참조에는 기능을 잘 드러내는 이름을 지어줄 수 있고 친절한 설명을 문서로 남길 수도 있다.
때론 람다가 메서드 참조보다 간결할 때가 있다. 주로 메서드와 람다가 같은 클래스에 있을 때 그렇다.
// 메서드 참조
service.execute(GoshThisClassNameIsHumongous::action);
// 람다(더욱 간결)
service.execute(() -> action());Integer::parseInt
// lambda
str -> Integer.parseInt(str)정적 참조와 비슷하다. 즉, 함수 객체가 받는 인수와 참조되는 메서드가 받는 인수가 똑같다.
Instant.now()::isAfter
// lambda
Instant then = Instant.now();
t -> then.isAfter(t)함수 객체를 적용하는 시점에 수신 객체를 알려준다. 이를 위해 수신 객체 전달용 매개변수가 매개변수 목록의 첫번째로 추가되며, 그 뒤로는 참조되는 메서드 선언에 매개변수들의 뒤따른다.
비한정적 참조는 주로 스트림 파이프라인에서 매핑과 필터 함수에 쓰인다.
String::toLowerCase
// lambda
str -> str.toLowerCase()TreeMap<K,V>::new
// lambda
() -> new TreeMap<K,V>()int[]::new
// lambda
len -> new int[len]인스턴스 메서드를 참조하는 두가지 유형.. 한정적, 비한정적
- 이해될듯 안될듯 어렴풋한 느낌입니다.
- 한정적은 해당 기능을 수행하기 위해서는 특정 인스턴스가 꼭 필요해서 한정적이라 한 것 같은데,
- 비한정적인 경우는 어떤 걸까요?
p261 ... 비한정적 참조에서는 함수 객체를 적용하는 시점에 수신 객체를 알려준다. 이를 위해서...
- 이 부분이 잘 이해가 안되서 그런지 비한정적이라 명명한 이유를 잘 모르겠네요.
- 한번 이야기 나눠보면 좋을 것 같아요
public 또는 protected로 정의한 메서드를 참조할 때 사용하며 static 메서드를 호출하는 것과 유사합니다. 주로 스트림에서 필터와 매핑용도로 많이 사용합니다. 여기서 비한정적이란 작성하는 구문 자체가 특정한 객체를 참조하기 위한 변수를 지정하지 않는다란 의미입니다. 예를 들어 보겠습니다.
String::toUpperCase
String 클래스의 toUpperCase 메서드는 public이나 static이 아니라 반드시 String 클래스가 객체화 되야 호출할 수 있습니다. 그런데 위 코드를 보면 static 메서드를 참조하는 것처럼 정의가 되어있습니다. 이 코드를 람다식으로 바꾸면
(String str) -> str.toUpperCase()
위 코드를 보시면 객체의 생성을 파라미터로 받았습니다. -> 람다 표현식 내부에서 객체 생성이 생겨 객체를 참조할 만한 변수가 외부에 존재하지 않습니다. 그래서 수신객체를 알려주는데 str::toUpperCase 라 표현할 수 없어 String::toUpperCase라 표현하고 String 이라는 수신객체를 알려줍니다.
만약 처리해야할 데이터가 위 사례처럼 하나면 상관없으나, 파라미터가 여러개면 너무 많이 생략하다보니 이 메서드 참조가 어디서 어디까지 한지 알 수가 없습니다. 그래서 주로 스트림에서 많이 사용하는 듯합니다. (매핑, 필터 작업)
이미 외부에서 선언된 객체의 메서드를 호출하거나, 객체를 직접 생성해서 메서드를 참조할 때 사용합니다. 여기서 한정적이란 메서드가 특정 객체의 변수로 제한되는걸 말합니다. 예시를 들어보겠습니다.
Calendar.getInstance()::getTime위 코드 보시면 :: 앞에 객체를 생성하는 코드가 작성 :: 뒤에는 해당 객체에서 호출할 수 있는 getTIme을 지정했습니다. 자바 8이후로 많은 클래스가 생성자를 이용해서 객체 인스턴스하는 것보단 of와 같은 메서들르 이용해서 생성하는 경향이 있습니다. 람다식으로 바꾸면 다음과 같습니다.
Calendar cal = Calendar.getInstance();
() -> cal.getTime()위 메서드 참조는 큰 단점이 있습니다. 메서드 참조에 의해서 값을 처리할 때마다 Calendar 객체를 생성하여 불필요한 낭비가 발생하게 됩니다. 그래서 한정적 메서드 참조할 때 외부에서 한 번 생성하고 참조하는 형식을 사용합니다.
Calendar cal = Calendar.getInstance(); // 객체 생성
cal::getTime // 메서드 참조. cal 변수를 참조합니다.
람다 표현식을 메서드 참조 방식으로 변경하면 더욱 간결하고 명확하게 표현할 수 있다. 또한 매개변수 수가 늘어날수록 메서드 참조로 제거할 수 있는 코드량도 늘어난다. 단, 람다가 메서드 참조보다 간결하게 표현되는 경우에는 람다식을 이용하는 편이 좋다.