# 55. 옵셔널 반환은 신중히 하라

<br>

## 값의 부재(Absence)를 어떻게 다룰 것인가?

자바 8 이전 : 값 부재 시 주로 예외 또는 null 반환 방식을 사용했습니다

<br>

### **예외(Exception) 던지기**

```java
// 예시: 사용자를 못 찾으면 예외 던지기
public User findUserOrThrow(String id) throws UserNotFoundException {
    User user = // ... ID로 사용자 찾는 로직 ...
    if (user == null) {
        throw new UserNotFoundException(id + " 사용자를 찾을 수 없음");
    }
    return user;
}
```

```java
try {
    User user = findUserOrThrow("someId");
} catch (UserNotFoundException e) {
    System.err.println(e.getMessage());
}
```

* 장점
  * 값 부재 상황을 호출자에게 강제로 알릴 수 있습니다 (특히 검사 예외)
* 단점
  * 예외는 진짜 '예외적' 상황에만 사용해야 합니다 (아이템 69)
  * 성능 비용
    * 스택 추적(stack trace) 전체를 캡처

<br>

### **null 반환**

```java
// 예시: 사용자를 못 찾으면 null 반환
public User findUserOrNull(String id) {
    User user = // ... ID로 사용자 찾는 로직 ...
    return user; // 찾으면 User 객체, 못 찾으면 null 반환
}
```

```java
User user = findUserOrNull("someId");
if (user != null) { // 매번 null 체크 필요!
    System.out.println("사용자 이름: " + user.getName());
} else {
    System.out.println("사용자를 찾을 수 없습니다.");
}
```

* 장점
  * 예외 처리에 비해 추가적인 성능 비용이 거의 없습니다
* 단점
  * **NullPointerException(NPE) 위험**
  * 코드 오염
    * 호출하는 코드마다 방어적인 null 체크 코드를 명시적으로 추가

***

\ <br>

## 자바 8의 대안: Optional

### **개념**

* `Optional<T>`는 **불변(immutable)** 컨테이너 객체
* 이 컨테이너는 내부에 T 타입의 값을 최대 한 개 담거나 (null이 아닌 값), 혹은 아무것도 담지 않을 수 있습니다
  * 값이 있으면 '존재한다(present)' 또는 '비어있지 않다(not empty)'
  * 값이 없으면 '비어있다(empty)'

### **목적 및 장점**

* 명시적 의사 전달
  * 메서드 반환 타입이 Optional라는 것 자체로 "이 메서드는 값을 반환하지 않을 수도 있습니다"라는 사실을 API 사용자(호출자)에게 명확히 알립니다
* NPE 방지
  * null을 직접 다루는 대신 Optional 객체를 통해 값의 존재 여부를 확인하고 안전하게 값을 추출하도록 유도함으로써 NPE 발생 가능성을 크게 줄입니다
* 처리 강제
  * Optional API를 사용해야만 내부 값에 접근할 수 있으므로, 개발자가 값 부재 상황을 인지하고 명시적으로 처리하도록 자연스럽게 유도합니다
* 유연성 및 가독성
  * 예외를 사용하는 것보다 유연하고(값 부재가 오류가 아님을 표현), null을 반환하는 것보다 API 사용 오류 가능성이 작으며 코드의 의도가 더 명확해집니다

***

\ <br>

## Optional 사용법: 생성 및 반환

Optional 객체를 만들고 반환하는 것은 간단합니다. 주로 정적 팩토리 메서드를 사용합니다.

<br>

### **Optional 객체 생성 방법**

* `Optional.empty()`
  * 비어있는 Optional 객체를 생성
  * 값이 없는 경우 사용
* `Optional.of(value)`
  * `value`를 담고 있는 Optional 객체를 생성
  * **주의: `value`가 `null`이면 NPE 발생!**
  * null이 아님을 확신할 때 사용
* `Optional.ofNullable(value)`
  * `value`가 `null`이 아니면 그 값을 담은 Optional을 생성
  * `null`이면 비어있는 Optional 객체를 생성
  * null일 가능성이 있는 값을 Optional로 변환할 때 안전하게 사용

<br>

### **예시: 컬렉션 최댓값 구하기**

* 예외 처리 방식

  ```java
  public static <E extends Comparable<E>> E max(Collection<E> c) {
      if (c.isEmpty())
          throw new IllegalArgumentException("빈 컬렉션");

      E result = null;
      // ... 최댓값 계산 ...

      return result;
  }
  ```

<br>

* Optional 반환 방식

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

      E result = null;
      // ... 최댓값 계산 ...

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

  * Optional 반환 구현이 어렵지 않다.

<br>

* Stream 버전 Optional 반환 방식

  ```java
  // 스트림 API 활용 시 더 간결
  public static <E extends Comparable<E>> Optional<E> maxStream(Collection<E> c) {
      return c.stream().max(Comparator.naturalOrder());
  }
  ```

  * 트림의 max() 같은 종단 연산 중 상당수가 옵셔널 반환

<br>

* **주의**
  * Optional을 반환 타입으로 사용하는 메서드에서 `null`을 반환하는 것은 절대 금물
  * Optional 도입 취지를 완전히 무시하는 행위

<br>

***

\ <br>

## Optional을 반환해야 하는 경우

> #### **핵심 원칙** : 메서드 실행 결과 **값이 없을 가능성이 있고, 그 상태를 호출자가 명시적으로 인지하고 처리하도록 강제하고 싶을 때** `Optional<T>` 반환을 고려

* 이는 **검사 예외(Checked Exception)의 취지와 유사**
* 즉, 이 메서드는 값을 안 줄 수도 있으니 대비하라고 API 레벨에서 알려주는 것
* '오류'가 아닌 '정상적일 수 있는 값의 부재'를 다룬다는 차이

\ <br>

## 반환된 Optional 처리 방법

<br>

### **기본값 지정** : `orElse(defaultValue)`

```java
String lastWordInLexicon = max(words).orElse("단어 없음...");
```

* Optional이 비어있으면 미리 제공된 `defaultValue`를 반환

<br>

### **예외 던지기** : `orElseThrow(exceptionSupplier)`

```java
Toy myToy = max(toys).orElseThrow(TemperTrantrumException::new);
```

* Optional이 비어있으면 지정된 예외를 발생
* 예외가 발생할 때만 예외 생성 비용 추가

<br>

### **값 직접 얻기 (주의!)** : `get()`

```java
Element lastNobleGas = max(elements.NOBLE_GASES).get();
```

* Optional에 값이 있으면 그 값을 반환하고, 잘못 판단한 것이라면 `NoSuchElementException`
* 값이 있다고 확신하는 경우가 아니라면 사용을 지양하고, `orElse` 등 다른 안전한 메서드를 추천

<br>

### **기본값 지연 계산** : `orElseGet(supplier)`

* Optional이 비어있을 때만 `supplier`를 호출하여 기본값을 생성
* 기본값 생성 비용이 클 때 유용

<br>

### **값 필터링** : `filter(predicate)`

* Optional에 값이 있고, 주어진 조건(predicate)을 만족하면 그 Optional을 그대로 반환
* 만족하지 않는다면 빈 Optional을 반환

<br>

### **값 변환** : `map(function)`

```java
Optional<String> parentPid =
    ph.parent().map(h -> String.valueOf(h.pid()));
```

* Optional에 값이 있으면 주어진 함수(function)를 적용하고, 그 결과를 담은 새로운 Optional을 반환
* 비어있으면 빈 Optional을 반환

<br>

### **중첩된 Optional 처리** : `flatMap(function)`

```java
streamOfOptionals.flatMap(Optional::stream);
```

* `map`과 유사하지만, 주어진 함수가 Optional을 반환할 때 사용
* 결과가 중첩된 `Optional<Optional<T>>`가 되지 않도록 한 단계 펼쳐줍니다

<br>

### **값이 있을 때만 동작 수행** : `ifPresent(consumer)`

```java
user.getEmail().ifPresent(email ->
    System.out.println("이메일: " + email));
```

* Optional에 값이 있을 때만 주어진 동작(consumer)을 수행

<br>

### **존재 여부 확인 (주의!)** : `isPresent()`

```java
// 부모 프로세스의 프로세스 ID를 출력하거나, 부모가 없다면 "N/A"를 출력
Optinal<ProcessHandel> parentProcess = ph.parent();
System.out.println("부모 PID: " +
    (parentProcess.isPresent() ?
        String.valueOf(parentProcess.get().pid()) : "N/A"));
```

```java
System.out.println("부모 PID: " +
    ph.parent()
    .map(h -> String.valueOf(h.pid()))
    .orElse("N/A"));
```

* 값이 있으면 `true`, 없으면 `false`를 반환
* `if (opt.isPresent()) { opt.get()... }` 패턴보다는 `ifPresent`, `map`, `orElse` 등을 사용하는 것이 더 간결하고 권장됩니다

\ <br>

## Optional을 사용하면 안 되는 경우

<br>

### 성능이 극도로 중요한 경우

* Optional 객체 생성 및 메서드 호출에는 약간의 오버헤드
* 성능 병목 지점에서는 사용에 신중해야 하며, 필요시 성능 측정을 통해(아이템 67) `null` 반환이나 예외 방식이 더 나을지 판단

<br>

### **컨테이너 타입 래핑**

* 컬렉션, 스트림, 배열 등 다른 컨테이너 타입을 Optional로 감싸서 반환 금지
  * `Optional<List<T>>`, `Optional<Map<K, V>>`, `Optional<Stream<T>>`, `Optional<T[]>`
* 대신 **빈 컨테이너(empty list, empty map 등)를 반환**
  * 아이템 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라
* 옵셔널 처리 코드를 생략하여 클라이언트 코드 간결함

<br>

### **박싱된 기본 타입 래핑**

* `Optional<Integer>`, `Optional<Long>`, `Optional<Double>`
* 이중 포장(값 + Optional 객체)으로 비효율적
* 전용 클래스인 `OptionalInt`, `OptionalLong`, `OptionalDouble`을 사용
* Boolean, Byte, Character, Short, Float 용은 없으므로 이 경우는 `Optional<T>` 사용 가능

<br>

### **컬렉션의 요소/키/값, 배열 요소**

* 컬렉션이나 배열의 원소로 Optional을 사용하는 것은 거의 항상 좋지 않습니다
* 예를 들어 `Map<K, Optional<V>>`는 키가 없는 경우와, 키는 있지만 값이 빈 Optional인 경우
* 쓸데없이 높아지는 복잡성으로 혼란과 오류 가능성

<br>

> 옵셔널은 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 게 적절한 상황은 거의 없다

<br>

### **인스턴스 필드**

* 필드 타입으로 Optional을 사용하는 것은 일반적으로 권장되지 않습니다
* 필드가 Optional이라는 것은 그 필드가 선택적(optional)임을 나타내는데, 이런 필드가 많다면 클래스 설계 자체를 재검토해야 할 가능성
  * 해당 필드를 갖는 하위 클래스를 만들거나, 다른 디자인 패턴 적용 고려
* 예외적 허용 (예: 빌더 패턴)
  * 다수의 선택적 필드
  * 기본 타입 필드(int, byte, ..)의 부재를 명확히 나타내야 할 때는 유용
  * 선택적 필드의 게터 메서드들이 옵셔널을 반환

\ <br>

***

## 신중한 Optional 반환 실천 체크리스트

<br>

### **DO**

* \[O] 메서드가 값을 반환하지 못할 '정상적인' 가능성이 있고, 호출자가 이 상황을 **반드시** 인지하고 대처해야 한다면 `Optional<T>` 반환을 고려
* \[O] 반환된 Optional을 처리할 때는 풍부한 API를 적극 활용하여 코드를 간결하고 안전하게 작성
* \[O] `int`, `double` 값을 Optional로 반환해야 한다면, `Optional<Integer>`, `Optional<Double>` 대신 성능이 좋은 `OptionalInt`, `OptionalDouble` 사용을 우선 고려
* \[O] `null`이 절대 아니라고 확신하는 값, `Optional.of(value)`를 사용
* \[O] `null`일 수도 있는 값, `Optional.ofNullable(value)`을 사용

### **DON'T**

* [x] **성능이 매우 민감한** 코드에서 **성능 측정 없이** 습관적으로 `Optional`을 반환 금지
* [x] `List`, `Set`, `Map`, `Stream`, 배열 등 **컨테이너 타입 자체를 `Optional`로 감싸서** 반환 금지
* [x] **박싱된 기본 타입을 담은 Optional 반환을 피하세요** (전용 Optional 클래스 사용)
* [x] Optional을 **컬렉션의 키(Key), 값(Value), 요소(Element)나 배열의 요소**로 사용하지 마세요 (거의 항상 나쁜 설계)
* [x] Optional을 **메서드 매개변수 타입**으로 사용하지 마세요 (오버로딩 등 다른 방법 고려)
* [x] Optional을 **인스턴스 필드 타입**으로 사용하는 것을 남용하지 마세요 (설계 재검토 필요 신호일 수 있음)
* [x] `Optional<T>`를 반환 타입으로 쓰는 메서드에서 **절대 `null`을 반환 금지**
* [x] `if (opt.isPresent()) { opt.get()... }` 패턴보다는 **다른 Optional API(map, orElse 등)를 먼저 고려**하여 코드를 더 간결하게 작성

***

\ <br>

## 핵심 요약

> #### `Optional<T>`는 자바 8 이후 값의 부재 가능성을 명확히 알리고 NPE를 방지하는 데 도움을 주는 강력하고 유용한 도구

> #### 주된 용도는 메서드의 반환 타입으로, 호출자에게 '값이 없을 수 있음'을 명시하고 관련 처리를 강제하고자 할 때 효과적

> #### Optional API(orElse, map, ifPresent 등)를 잘 활용하면 코드를 더 선언적이고 안전하며 간결하게 작성 가능

> #### 성능, 컨테이너 래핑, 박싱된 기본 타입, 컬렉션/배열 요소, 필드 등에는 사용을 신중히 하거나 피해야 합니다

> #### Optional은 만병통치약이 아니므로, 상황에 맞게 적절히 사용해야 합니다
