Item55. Optional 반환은 신중히 하라
Optional API
jdk 8 이전에는 method가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지는 null을 반환하거나 , 예외를 던지는 방법이 있었다.
예외를 던지는 경우에는 진짜 예외적인 상황에만 사용해야 하며, null을 반환하는 경우에는 null처리로직이 client코드에 들어가야한다는 단점이 있었다.
jdk 8 이후부터는 Optional API 가 추가되었다.
Optional<T> 는 null 이 아닌 T 타입의 참조값을 하나 담거나, 혹은 아무것도 담지 않을 수 있다.
보통은 T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야 할때, T 대신 Optional<T>를 반환하도록 선언하면 된다.
1 | // collection에서 최댓값을 구하는 method로 collection이 비어있으면 IllegalArgumentException을 던진다. |
위 method를 Optional을 반환하도록 변경하면 다음과 같다.
1 | public static <E extends Comparable<E>> Optional<E> max(Collection<E> c){ |
- 빈 optional은 Optional.empty()
- null이 아닌 값이 들어있는 optional은 Optional.of(value)
- null일 수 있는 값이 들어있는 optional은 Optional.ofNullable(value)를 사용하면 된다.
주의사항은 optional을 반환하는 method에는 당연히 null을 반환하면 안된다. 이는 API 도입 취지를 완전히 무시하는 행동이다.
추가로 Stream의 max연산을 비롯한 상당수의 종단 연산이 Optional 반환을 지원한다.
1 | public static <E extends Comparable<E>> Optional<E> max(Collection<E> c){ |
Optional 반환 기준
null을 반환하거나 예외를 던지는 대신 Optional 반환을 선택해야 하는 기준은 무엇인가?
Optional을 반환할때 의도는 Optional안에 든 값이 비어있을수도 있음을 API 사용자에게 명확하게 알려준다.
따라서 결과가 없을 수 있으며, client가 결과가 없을때 상황을 특별하게 처리해야 한다면 Optional을 반환하는게 좋다.
예를 들면 Optional 사용자는 값이 비어있었을때 기본값을 아래와 같이 설정해둘 수 있다.
1 | String result = max(words).orElse("word not exist"); |
또는 상황에 맞게 예외를 던질 수 있다.
1 | max(words).orElseThrow(NoMaxValException::new); |
항상 값이 채워져 있다고 보장되는 상황이라면 바로 꺼내도 무관하나, 값이 없다면 NoSuchElementException이 발생할 것이다.
1 | max(Elements.NOBLE_GASES).get(); // 값이 없는 경우 NoSuchElementException |
이따금 기본값을 설정하는 비용이 아주 클 경우에는 Supplier<T> 를 인수로 받는 orElseGet을 사용하면
값이 필요할 때, Supplier<T>를 사용해 생성하므로 초기 설정 비용을 낮출 수 있다.
optional 이 제공하는 여러 method중에 활용할 수 있는게 없다면 isPresent method를 활용하면 된다.
isPresent method는 optional이 채워져 있으면 true를, optional이 비어 있으면 false를 반환한다.
1 | /** |
1 | Optional\<ProcessHandle> parentProcess = ph.parent(); |
isPresent method는 다음과 같이 다듬을 수 있다.
1 | System.out.println("부모 PID: " + ph.parent().map(h->String.valueOf(h.pid())).orElse("N/A")); |
Stream을 사용한다면 optional들을 Stream<Optional<T>> 로 받아서, 그 중 채워진 Optional들에서 값을 뽑아,
Stream<T> 에 건너 담아 처리하는 경우가 많다.
1 | streamOfOptionals |
jdk 9에서는 Optional을 stream으로 변환해주는 stream() method가 추가되었다.
optional에 값이 있으면 그 원소를 담은 stream 으로 , 값이 없다면 빈 stream으로 변환한다.
1 | /** |
이를 Stream의 flatMap method와 조합하면 앞의 코드를 다음처럼 변경할 수 있다.
1 | streamOfOptionals |
collection과 같은 container 타입은 optional로 감싸면 안된다.
빈 optional을 반환하기 보다 빈 List를 반환하는게 client가 optional 처리 코드를 넣지 않아도 되기 떄문이다.
추가적으로 박싱된 기본 타입을 담는 optional은 값을 2번 담기 떄문에 기본 타입보다 당연히 무거울 수 밖에 없는데,
Optional API 는 기본적으로 int,long,double 전용 Optional class들인 OptionalInt, OptionalLong, OptionalDouble을 제공한다.
예외
Optional은 map 의 key로 사용하면 안된다. key가 없을때의 처리로직이 복잡해진다.
인스턴스 필드로 Optional을 갖는 경우는 대부분 바람직하지 않으나, 선택적으로 필드값을 주입받을때는 유용할 수도 있다.