Skip to content

Commit 55018d8

Browse files
committed
[Meet-Coder-Study#55][B팀] 옵셔널 반환은 신중히 하라
1 parent 88e53bc commit 55018d8

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# [Effective Java] item 55. 옵셔널 반환은 신중히 하라
2+
3+
### 메서드가 특정 조건에서 값을 반환할 수 없을 때
4+
#### 자바 8 이전의 방식의 단점
5+
1. 예외를 던진다.
6+
- 예외는 진짜 예외적인 상황에서만 사용해야 한다.
7+
- 예외를 생성할 때 스택 추적 전체를 캡쳐하므로 비용도 만만치 않다.
8+
2. null을 반환한다.
9+
- null을 반환할 수 있는 메서드를 호출할 때는, (null이 반환될 일이 절대 없다고 확신하지 않는 한) 별도의 null 처리 코드를 추가해야 한다.
10+
11+
12+
#### 자바 8부터 생긴 `Optional<T>`
13+
- `Optional<T>`는 null이 아닌 T 타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다.
14+
- 아무것도 담지 않은 옵셔널은 '비었다'고 말한다. 반대로, 어떤 값을 담은 옵셔널은 '비지 않았다'고 한다.
15+
- `옵셔널은 원소를 최대 1개 가질 수 있는 '불변' 컬렉션`이다.
16+
- `보통은 T를 반환하지만, 특정 조건에서는 아무것도 반환하지 않아야 할 때 T 대신 Optional<T>를 반환`하도록 선언하면 된다. 그러면 유효한 반환 값이 없을 때는 빈 결과를 반환하는 메서드가 만들어진다.
17+
- 옵셔널을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉬우며, null을 반환하는 메서드보다 오류 가능성이 적다.
18+
19+
##### 컬렉션에서 최댓값을 구한다(컬렉션이 비어있으면 예외를 던진다)
20+
```java
21+
public static <E extends Comparable<E>> E max(Collection<E> c) {
22+
if (c.isEmpty())
23+
throw new IllegalArgumentException("Empty collection");
24+
25+
E result = null;
26+
for (E e : c)
27+
if (result == null || e.compareTo(result) > 0)
28+
result = Objects.requireNonNull(e);
29+
30+
return result;
31+
}
32+
```
33+
34+
##### 컬렉션에서 최댓값을 구해 Optional<E>로 반환한다.
35+
```java
36+
public static <E extends Comparable<E>>
37+
Optional<E> max(Collection<E> c) {
38+
if (c.isEmpty())
39+
return Optional.empty();
40+
41+
E result = null;
42+
for (E e : c)
43+
if (result == null || e.compareTo(result) > 0)
44+
result = Objects.requireNonNull(e);
45+
46+
return Optional.of(result);
47+
}
48+
```
49+
50+
위와 같이 빈 컬렉션을 건냈을 때 IllegalArgumentException을 던지는 것보다 `Optional<E>`를 반환하는 편이 더 낫다.
51+
52+
- `Optional.empty()`: 빈 Optional을 만드는 메서드
53+
- `Optional.of(value)`: 값이 든 Optional을 만드는 메서드
54+
- (주의) `Optional.of(value)`에 null을 넣으면 NullPointException을 던진다. null 값도 허용하는 Optional을 만들려면 `Optional.ofNullable(value)`를 사용해야 한다.
55+
- `Optional을 반환하는 메서드에서는 절대 null을 반환하지 말자`
56+
57+
#### 스트림의 종단 연산에서 옵셔널을 반환하는 방식을 사용할 경우
58+
##### 컬렉션에서 최댓값을 구해 `Optional<E>`로 반환한다. - 스트림 버전
59+
```java
60+
public static <E extends Comparable<E>>
61+
Optional<E> max(Collection<E> c) {
62+
return c.stream().max(Comparator.naturalOrder());
63+
}
64+
```
65+
66+
#### Optional을 반환했을 때의 장점
67+
- 옵셔널은 검사 예외와 취지가 비슷하다. 즉, 반환 값이 없을 수도 있음을 API 사용자에게 명확히 알려준다. 이렇게 검사 예외를 던진다면 클라이언트는 반드시 이에 대해 대처하는 코드를 작성해야 한다.
68+
69+
#### Optional을 반환했을 때 클라이언트가 취할 행동
70+
71+
##### 옵셔널 활용 1 - 기본 값을 정해둘 수 있다.
72+
```java
73+
String lastWordInLexicon = max(words).orElse("단어 없음...");
74+
```
75+
76+
##### 옵셔널 활용 2 - 원하는 예외를 던질 수 있다.
77+
```java
78+
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
79+
```
80+
81+
##### 옵셔널 활용 3 - 항상 값이 채워져 있다고 가정한다. (NoSuchElementException을 주의한다)
82+
```java
83+
Element lastNobleGas = max(Elements.NOBLE_GASES).get();
84+
```
85+
86+
##### 옵셔널 활용 4 - 기본 값 설정 비용이 클 경우
87+
```java
88+
public T orElse(T other)
89+
90+
public static String orElseBenchmark() {
91+
return Optional.of("baeldung").orElse(getRandomName());
92+
}
93+
```
94+
```java
95+
public T orElseGet(Supplier<? extends T> other)
96+
97+
public static String orElseGetBenchmark() {
98+
return Optional.of("baeldung").orElseGet(() -> getRandomName());
99+
}
100+
```
101+
- `Supplier<T>`를 인수로 받는 `orElseGet`을 사용하면 초기 설정 비용을 낮출 수 있다.
102+
103+
##### 옵셔널 활용 5 - isPresent 메서드
104+
- 옵셔널이 채워져 있으면 true, 비어있으면 false를 반환
105+
- 원하는 모든 메서드를 수행할 수 있으나 신중히 사용하자. isPresent를 사용하기 전에 앞의 옵셔널 활용 1~4로 표현할 수 있는지 면밀히 검토하자. 그 편이 더 짧고 명확하며 용법에 맞는 코드이다.
106+
```java
107+
public class ParentPid {
108+
public static void main(String[] args) {
109+
ProcessHandle ph = ProcessHandle.current();
110+
111+
// Inappropriate use of isPresent
112+
Optional<ProcessHandle> parentProcess = ph.parent();
113+
System.out.println("Parent PID: " + (parentProcess.isPresent() ?
114+
String.valueOf(parentProcess.get().pid()) : "N/A"));
115+
116+
// Equivalent (and superior) code using orElse
117+
System.out.println("Parent PID: " +
118+
ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));
119+
}
120+
}
121+
```
122+
##### 스트림을 사용할 경우
123+
124+
혹은 java8의 stream을 이용하여 아래와 같이 표현할 수 있다. 옵셔널에 값이 있다면 (Optional::isPresent) 그 값을 꺼내 (Optional::get) 스트림에 매핑한다.
125+
```java
126+
streamOfOptionals
127+
.filter(Optional::isPresent)
128+
.map(Optional::get)
129+
```
130+
131+
자바 9부터는 Optional을 stream으로 변환해주는 Optional.stream() 메서드도 추가되엇다. 옵셔널에 값이 있으면 그 값을 원소로 담은 스트림으로, 값이 없다면 빈 스트림으로 변환한다. 이를 Stream의 flatMap 메서드와 조합하면 아래와 같이 바꿀 수 있다.
132+
133+
```java
134+
streamOfOptionals.flatMap(Optional::stream)
135+
```
136+
137+
#### 옵셔널을 사용하면 안되는 경우
138+
- `컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다.`
139+
- 빈 `Optional<List<T>>`를 반환하기보다는 빈 `List<T>`를 반환하는 게 좋다. 빈 컨테이너를 그대로 반환하면 클라이언트에 옵셔널 처리 코드를 넣지 않아도 된다.
140+
141+
#### 옵셔널을 사용하면 좋은 경우
142+
- `결과를 알 수 없으며, 클라이언트가 이 상황을 특별하게 처리해야 한다`면 `Optional<T>`를 반환한다.
143+
- 단, Optional을 사용하면 새로 할당하고 객체를 초기화하며 값을 꺼내기 위해 메서드를 호출하는 한 단계를 더 거치기 때문에 성능이 저하될 수 있다. 따라서, `성능이 중요한 상황에는 옵셔널이 맞지 않을 수 있다.`
144+
145+
#### int, long, double 전용 옵셔널 클래스가 있음을 기억하고, 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자
146+
- OptionalInt
147+
- OptionalLong
148+
- OptionalDouble
149+
150+
#### 옵셔널을 맵의 값으로 사용하면 절대 안된다.
151+
- 맵 안에 키가 없다는 사실을 나타내는 방법이 두 가지가 된다.
152+
1. 하나는 키 자체가 없는 경우
153+
2. 키는 있지만 그 키가 속이 빈 옵셔널인 경우
154+
- 복잡성을 높여서 오류 가능성을 키우기 때문에 사용하지 말자. 더 일반화하여 이야기하면 `옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 게 적절한 상황은 거의 없다.`
155+
156+
---
157+
158+
### 핵심 정리
159+
- 값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환 값이 없을 가능성을 염두에 둬야 하는 메서드라면 옵셔널을 반환해야할 상황일 수 있다.
160+
- 하지만 옵셔널 반환에는 성능 저하가 뒤따르니, 성능에 민감한 메서드라면 null을 반환하거나 예외를 던지는 편이 나을 수도 있다.
161+
- 옵셔널을 반환값 이외의 용도로 쓰는 경우는 매우 드물다.
162+
163+
### 참고 자료
164+
- Effective Java 3/E

0 commit comments

Comments
 (0)