Skip to content

Commit

Permalink
[6회차] 아이템 42, 43 - 배키(백은희) (#58)
Browse files Browse the repository at this point in the history
* 아이템 43

* 아이템 42
  • Loading branch information
ehBeak committed May 24, 2024
1 parent 4bb9bf4 commit 188126e
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 0 deletions.
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 정적 중첩 클래스의 인스턴스를 사용하자.(🤔)
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)


## 결론
> 메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 쓰고, 그렇지 않을 때만 람다를 사용하라.

0 comments on commit 188126e

Please sign in to comment.