-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 아이템 43 * 아이템 42
- Loading branch information
Showing
2 changed files
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# [item 42] 익명 클래스보다는 람다를 사용하라 | ||
|
||
> 함수 객체 | ||
> 추상 메서드를 하나만 담은 인터페이스의 객체 | ||
> 특정 함수나 동작을 나타내는 데 씀 | ||
## 함수 객체를 만드는 법 | ||
### JDK 1.1이 등장한 이후, Java8이 등장하기 전 | ||
익명 클래스로 객체를 생성했다. | ||
함수 인터페이스에 해당하는 Comparator의 익명 클래스로 객체를 선언해보자. | ||
```java | ||
Collections.sort(words, new Comparator<String>() { | ||
public int compare(String s1, String s2) { | ||
return Integer.compare(s1.length(), s2.length()); | ||
} | ||
}) | ||
``` | ||
**단점** | ||
* 코드가 너무 길기 때문에 함수형 프로그래밍에 적합하지 않다.(때문에 자바는 함수형 프로그래밍에 적합하지 않은 언어였다.) | ||
|
||
### Java8이 등장한 이후 | ||
람디식으로 객체를 생성했다. | ||
위의 Comparator 함수를 람다식을 이용해 표현하면 아래와 같은 코드이다. | ||
```java | ||
Collections | ||
.sort(words, | ||
(s1, s2) -> Integer.compare(s1.length(), s2.length())); | ||
``` | ||
|
||
> 참고 | ||
> 위의 코드를 더 간결하게 표현할 수 있다. | ||
> ```java | ||
> // (대안1) 람다 자리에 생성 메서드 사용 | ||
> Collections.sort(words, comparingInt(String::length)); | ||
> // (대안2) List 인터페이스에 추가된 sort 메서드를 이용하면 더욱 짧아진다. | ||
> words.sort(comparingInt(String::length)); | ||
> ``` | ||
> 참고 | ||
> 람다식의 동작 방식과 타입 추론 | ||
> 매개변수에 타입이 없는 이유는 컴파일러가 문맥에 맞춰 타입을 추론해 줬기 때문이다. | ||
> 컴파일러는 제네릭을 통해 타입을 추론한다. 때문에 반드시 제네릭을 올바르게 써야 한다. | ||
> ```java | ||
> Collections | ||
> .sort(words, // List<String> 이 아니라, List타입이라면? | ||
> (s1, s2) -> Integer.compare(s1.length(), s2.length())); | ||
> ``` | ||
> 위 코드의 words가 List<String>이 아니라 List타입이라면 컴파일러는 제네릭을 통해 타입 추론이 불가하다. | ||
> 그 결과 컴파일 오류가 났을 것이다. | ||
## enum에서 람다를 사용하는 법 | ||
아래 코드와 같이 DoubleBinaryOperator를 필드로 선언하여 람다로 값을 넣어줄 수 있다. | ||
```java | ||
enum Operation { | ||
PLUS ("+", (x, y) -> x + y), | ||
MINUS ("-", (x, y) -> x - y), | ||
TIMES ("*", (x, y) -> x * y), | ||
DIVIDE ("/", (x, y) -> x / y); | ||
private final String symbol; | ||
private final DoubleBinaryOperator op; | ||
Operation(String symbol, DoubleBiinaryOperator op) { | ||
this.symbol = symbol; | ||
this.op = op; | ||
} | ||
``` | ||
> 참고 | ||
> 그러면, 상수별 클래스 몸체는 더이상 사용할 이유가 없을까? | ||
> 아니다! 람다는 이름도 없고 문서화도 못한다. 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수 가 많아지면 람다를 사용하지 말자. | ||
## 람다를 대체할 수 없는 곳(익명 클래스를 사용해야 하는 곳) | ||
* 추상 메서드가 여러개인 인터페이스의 인스턴스를 만들 때 | ||
* 추상 클래스를 생성할 때 | ||
## 람다 주의사항 | ||
* 람다는 한 줄일 때 가장 좋다. 세줄 이상 넘어가면 가독성이 나빠진다. | ||
* 람다는 함수형 인터페이스에서만 쓰인다. | ||
* 람다는 자신을 참조할 수 없다. 람다 내부의 this 키워드는 바깥 인스턴스를 가리킨다. 자신을 참조하고 싶다면 익명 클래스를 사용하자. | ||
* 직렬화해야만 하는 함수 객체가 있다면 private 정적 중첩 클래스의 인스턴스를 사용하자.(🤔) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# [item 42] 람다보다는 메서드 참조를 사용하라 | ||
|
||
## 메서드 참조의 장점 | ||
우리는 **함수 객체를 더 간결하게 생성할 수 있다**는 이유로 람다를 사용했다. | ||
그러나 더 간결하게 메서드 참조를 이용해 더 간결하게 만들 수 있다. | ||
|
||
아래 코드를 보자. 람다를 사용한 방식이다. | ||
```java | ||
map.merge(key, 1, (count, incr) -> count + incr); | ||
``` | ||
|
||
> 동작 방식 | ||
> * 만약 map에 주어진 key 값이 없다면 | ||
> * 주어진 key와 값(현재는 1)을 저장한다. | ||
> * 만약 map에 주어진 key 값이 존재한다면 | ||
> * key에 해당하는 값을 3번째 인자인 함수로 계산해서 덮어 씌운다. | ||
|
||
위의 람다를 사용한 방식은 간결해 보이지만, **매개변수인 count와 incr은 크게 하는 일이 없이 공간을 차지한다.** | ||
|
||
두개의 값을 더하는 로직은 Integer 클래스의 sum이라는 함수를 대신 사용할 수 있다. | ||
아래와 같이 메서드 참조를 이용해보자. | ||
```java | ||
map.merge(key, 1, Integer::sum); | ||
``` | ||
이와 같이 메서드 참조를 이용하면 아래와 같은 장점이 있다. | ||
* 간결하다. | ||
* 기능을 잘 드러내는 이름을 지어줄 수 있다. | ||
|
||
|
||
## 메서드 참조보다 람다를 더 선호하는 상황 | ||
대부분 간결하다는 이유로 람다보다 메서드 참조를 선호하지만, 람다를 더 선호하는 상황도 있다. | ||
아래와 같은 상황이다. | ||
* 매개변수의 이름 자체가 프로그래머에게 좋은 가이드가 될 때 | ||
* 람다의 구현부가 너무 길거나 복잡할 때 | ||
|
||
위와 같은 예시로는 Function.identy()가 있다. | ||
Function.identity()는 x -> x 꼴의 함수이다. | ||
완전히 개발자가 코드를 읽는다는 가정 하에 생각한다면 `x->x`가 읽기 쉽다는 걸 알 수 있다. | ||
([사실 성능에는 차이가 있다고 한다!](https://juno-juno.tistory.com/103)) | ||
|
||
|
||
## 메서드 참조 유형 | ||
**정적 참조** | ||
클래스의 정적 메서드를 참조한다. (Class::staticMethod) | ||
|
||
**인스턴스 메서드 참조** | ||
클래스의 인스턴스 메서드를 참조한다. (Class::instanceMethod) | ||
* 한정적 인스턴스 메서드 참조 | ||
* 정적 참조와 비슷하다. 참조 메서드는 생성된 객체에 속해 있다. | ||
* 비한정적 인스턴스 메서드 참조 | ||
* 참조하는 메서드를 호출할 때, 어떤 객체의 메서드를 호출하는지 매개변수로 보낸다. | ||
```java | ||
// 한정적 인스턴스 메서드 참조 예 | ||
Instant.now()::isAfter | ||
|
||
Instant then = Instant.now(); | ||
t -> then.isAfter(t); // 람다식으로 표현 : 매개변수로 받은 객체를 그대로 사용한다. (받은 객체의 값을 호출하는 것이 아닌, 그대로 넘긴다.) | ||
``` | ||
```java | ||
// 비한정적 인스턴스 메서드 참조 예 | ||
String::toLowerCase | ||
|
||
str -> str.toLowerCase(); // 람다식으로 표현 : 매개변수로 받은 객체의 함수를 호출한다. | ||
``` | ||
|
||
**클래스 생성자 참조 메서드** | ||
인스턴스를 생성할 때, 사용하는 메서드이다. (Class::new) | ||
|
||
**배열 생성자 참조 메서드** | ||
배열을 생성할 때, 사용하는 메서드이다. (int[]::new) | ||
|
||
|
||
## 결론 | ||
> 메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 쓰고, 그렇지 않을 때만 람다를 사용하라. |