Skip to content

Commit

Permalink
[item55]: 옵셔널 반환은 신중히 하라 (#127)(건호) (#133)
Browse files Browse the repository at this point in the history
* [Item03]: private 생성자나 열거 타입으로 싱글턴임을 보증하라 (#5)(건호)

* docs: chore

* [Item08]: finalizer와 cleaner 사용을 피하라 (#13)(건호)

* [Item29] 이왕이면 제네릭 타입으로 만들라 (#23)(건호)

* [item42]: 익명 클래스보다는 람다를 사용하라 (#33)

* [item47]: 반환타입으로는 스트림보다 컬렉션이 낫다

* prep: item 81

* [Item81]: wait와 notify는 동시성 유틸리티를 애용하라 (#53) (건호)

* chore wait와_notify보다는_동시성_유틸리티를_애용하라.md

* [item86]: Serializable을 구현할지는 신중히 결정하라 (#63)

* [Item15]: 클래스와 멤버의 접근 권한을 최소화하라 (#80)(건호)

* [Item10]: equals는 일반 규약을 지켜 재정의하라 (#73)(건호)

* [item55]: 옵셔널 반환은 신중히 하라 (#127)(건호)

---------

Co-authored-by: Gunho Park <[email protected]>
Co-authored-by: devgun <[email protected]>
Co-authored-by: Gunho Park <[email protected]>
  • Loading branch information
4 people authored Sep 3, 2023
1 parent fdb9b0c commit 5ccd951
Showing 1 changed file with 197 additions and 0 deletions.
197 changes: 197 additions & 0 deletions Ch08/item55/옵셔널_반환은_신중히_하라.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Item 55. 옵셔널 반환은 신중히 하라

Java 8 이전에서 메서드가 특정 조건에서 값을 반환할 수 없는 경우의 일반적인 상황은 다음과 같다:

- 예외 발생: 메서드 내에서 예외가 발생하여 정상적인 실행 흐름이 방해되는 경우.
- 조건문: 메서드 내의 로직에서 특정 조건을 만족하지 못하여 반환 값을 정의할 수 없는 경우.
아래는 간단한 예시이다:

```java
public Integer divide(int numerator, int denominator) {
if (denominator == 0) {
// 분모가 0인 경우 나누기 연산을 할 수 없다.
return null; // null 반환
}

return numerator / denominator;
}
```

- 이 예시에서, 분모가 0일 경우 나누기 연산은 수행될 수 없으므로, 이 조건을 검사하고 null을 반환한다.
- 이러한 방식은 권장되지 않는다. 왜냐하면 null 반환은 NullPointerException의 원인이 될 수 있기 때문이다.
- 따라서, 이런 경우 예외를 발생시키는 것이 더 바람직하다.

_즉, 위의 경우의 선택지는 크게 2가지였는데,_

**1. 예외를 던진다.**

- 예외는 진짜 예외적인 상황에서만 사용해야 한다.
- 예외를 생성할 때 스택 추적 전체를 캡쳐하므로 비용도 만만치 않다.

**2. null을 반환한다.**

- null을 반환할 수 있는 메서드를 호출할 때는, (null이 반환될 일이 절대 없다고 확신하지 않는 한) 별도의 null 처리 코드를 추가해야 한다.

<br/>

### Optional의 등장

- 자바 8이 되면서 Optional<T>을 사용할 수 있게 되었다.
- 옵셔널은 T타입을 담고있거나, 아무것도 담지 않을 수 있다.
- 옵셔널은 원소를 최대 1개 가질 수 있는 불변 컬렉션이다. (실제 collection을 구현한 것은 아니다.)
- 보통 T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야할 때 T 대신 Optional<T>를 반환하도록 선언한다.
- 옵셔널은 예외를 던지는 메서드보다 유연하고 null을 반환하는 메서드보다 오류 가능성이 적다.

**예시1) 컬렉션에서 최댓값을 구한다(컬렉션이 비어있으면 예외를 던진다.)**

```java
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("Empty collection");

E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);

return result;
}
```

**예시2) 컬렉션에서 최댓값을 구해 Optional<E>로 반환한다.**

```java
public static <E extends Comparable<E>>
Optional<E> max(Collection<E> c) {
if (c.isEmpty())
return Optional.empty();

E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);

return Optional.of(result);
}
```

위와 같이 빈 컬렉션을 건냈을 때 IllegalArgumentException을 던지는 것보다 Optional<E>를 반환하는 편이 더 낫다.

- Optional.empty(): 빈 Optional을 만드는 메서드
- Optional.of(value): 값이 든 Optional을 만드는 메서드
- (주의) Optional.of(value)에 null을 넣으면 NullPointException을 던진다.
- null 값도 허용하는 Optional을 만들려면 Optional.ofNullable(value)를 사용해야 한다.
- Optional을 반환하는 메서드에서는 절대 null을 반환하지 말자

<br/>

### 언제 사용해야 하는가?

- 책에서는 checkedExeption과 취지가 비슷하다고 설명한다.
- 반환값이 없을 수도 있음을 사용자에게 명확히 알려준다는 점에서만 비슷하다고 생각하면 된다.

- 결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional을 반환한다.
- 하지만 Optional도 엄연히 새로 할당하고 초기화해야 되는 객체이고 안에서 값을 꺼내려면 메서드를 호출하는 과정이 필요하다.
- 따라서 성능이 중요한 상황이라면 세심히 측정해보는 방법밖에 없다.

<br/>

### Optional을 처리하는 방법

**기본값을 설정할 수 있다.**

```java
String find1 = something.orElse("NONE");
```

**원하는 예외를 던질 수 있다.**

```java
String find2 = something.orElseThrow(IllegalArgumentException::new);
```

**항상 값이 채워져 있다고 가정한다.**

```java
String find3 = something.get();
```

**OrElse()에 설정하는 기본값은 Optional의 값이 있어도 생성된다.**

```java
Optional<String> something = findSomething(List.of("something"));

String find1 = something.**orElse**(nothing());

String find2 = something.**orElseGet**(() -> nothing());

static String nothing() {
System.out.println("나 생성됐어");
return "No";
}
```

- orElse는 Optional의 값이 null이 아니더라도 생성되는 단점이 있다.
- 따라서 비용이 큰 값을 설정하게 된다면 부담이 될 수 있다.
- 그럴때 OrElseGet을 사용하면 초기 설정 비용을 낮출 수 있다.

<br/>

### 특별한 쓰임에 대비한 메서드

- Optional 클래스에서 filter, map, flatmap의 메서드도 사용할 수 있다.
- Stream API가 제공하는 메서드와 동일한 기능을 수행한다.
- 기본 메서드로 해결하기 어렵다면 위 메서드로 해결이 가능한지 검토해본다.

**isPresent() 메서드**

적합한 메서드를 찾지 못 했다면 isPresent() 메서드를 살펴보자. 값이 있다면 true, 없다면 false를 반환한다. 하지만 앞에 소개한 메서드로 대부분의 해결이 가능하므로 더 짧고 명확한 코드를 사용한다.

```java
if (findSomething(List.of("something")).isPresent()) {
System.out.println("찾았다.");
} else {
System.out.println("못 찾았다.");
}
```

**strem() 메서드**

자바 9부터 Optional에 stream() 메서드가 추가되었다. OptionalStream으로 변환해주는 어댑터다.
값이 있다면 값을 담은 스트림으로, 값이 없다면 빈 스트림으로 변환한다.

<br/>

### 정리

OptionalJava 8부터 도입된 컨테이너 객체로, 값이 있을 수도 있고 없을 수도 있는 상황을 표현한다. Optional의 사용에 대한 내용을 정리하면 다음과 같다:

**언제 Optional을 사용해야 하는가?**

- 메서드가 특정 조건에서 값을 반환할 수 없을 때.
- 반환 값이 없을 수도 있음을 API 사용자에게 명확히 알려주고 싶을 때.
- 예외를 던지는 것보다 유연한 반환을 원할 때.
- null을 반환하는 것보다 오류 가능성을 줄이고 싶을 때.

**int, long, double 전용 옵셔널 클래스가 있음을 기억하고, 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자**

- OptionalInt
- OptionalLong
- OptionalDouble

**언제 Optional을 사용하지 말아야 하는가?**

- 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입을 감싸서는 안 된다.
- 옵셔널을 맵의 값으로 사용하면 안 된다.
- 성능이 중요한 상황에서는 옵셔널의 사용을 피해야 한다.

**Optional을 사용해야 하는가?**

- 예외를 던지는 것보다 유연하고 사용하기 쉽다.
- null을 반환하는 것보다 오류 가능성이 적다.
- API 사용자에게 반환 값의 유무를 명확히 알려줄 수 있다.
- 예외 생성 시 스택 추적의 비용을 피할 수 있다.

_Optional은 값이 있을 수도, 없을 수도 있는 상황을 표현하며, API 사용자에게 이를 명확히 알려준다._
_Optional의 사용은 코드의 유연성과 안정성을 높여준다._
_그러나 성능이 중요한 상황이나 특정 컨테이너 타입을 감싸는 용도로는 사용하지 않는 것이 좋다._

0 comments on commit 5ccd951

Please sign in to comment.