-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
fdb9b0c
commit 5ccd951
Showing
1 changed file
with
197 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,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() 메서드가 추가되었다. Optional을 Stream으로 변환해주는 어댑터다. | ||
값이 있다면 값을 담은 스트림으로, 값이 없다면 빈 스트림으로 변환한다. | ||
|
||
<br/> | ||
|
||
### 정리 | ||
|
||
Optional은 Java 8부터 도입된 컨테이너 객체로, 값이 있을 수도 있고 없을 수도 있는 상황을 표현한다. Optional의 사용에 대한 내용을 정리하면 다음과 같다: | ||
|
||
**언제 Optional을 사용해야 하는가?** | ||
|
||
- 메서드가 특정 조건에서 값을 반환할 수 없을 때. | ||
- 반환 값이 없을 수도 있음을 API 사용자에게 명확히 알려주고 싶을 때. | ||
- 예외를 던지는 것보다 유연한 반환을 원할 때. | ||
- null을 반환하는 것보다 오류 가능성을 줄이고 싶을 때. | ||
|
||
**int, long, double 전용 옵셔널 클래스가 있음을 기억하고, 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자** | ||
|
||
- OptionalInt | ||
- OptionalLong | ||
- OptionalDouble | ||
|
||
**언제 Optional을 사용하지 말아야 하는가?** | ||
|
||
- 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입을 감싸서는 안 된다. | ||
- 옵셔널을 맵의 값으로 사용하면 안 된다. | ||
- 성능이 중요한 상황에서는 옵셔널의 사용을 피해야 한다. | ||
|
||
**왜 Optional을 사용해야 하는가?** | ||
|
||
- 예외를 던지는 것보다 유연하고 사용하기 쉽다. | ||
- null을 반환하는 것보다 오류 가능성이 적다. | ||
- API 사용자에게 반환 값의 유무를 명확히 알려줄 수 있다. | ||
- 예외 생성 시 스택 추적의 비용을 피할 수 있다. | ||
|
||
_Optional은 값이 있을 수도, 없을 수도 있는 상황을 표현하며, API 사용자에게 이를 명확히 알려준다._ | ||
_Optional의 사용은 코드의 유연성과 안정성을 높여준다._ | ||
_그러나 성능이 중요한 상황이나 특정 컨테이너 타입을 감싸는 용도로는 사용하지 않는 것이 좋다._ |