[교재 EffectiveJava] 아이템 55. 옵셔널 반환은 신중히 하라
- Book/Effective Java
- 2021. 11. 26.
빈 값 처리방법 Optioanl
자바 8 전에는 메서드가 특정 조건에서 값을 반환할 수 없을때 취할 수 있는 선택지는 두가지였다.
1) 예외를 던진다.
2) null 을 반환한다. (반환 타입이 객체 참조일 경우)
두 가지 방법 모두 허점이 있다. 예외는 진짜 예외적인 상황에서 사용해야한다. null을 반환하면 클라이언트에서 별도의 null 처리 코드를 추가해야한다.
자바 8 후에 한가지 방법이 생겼다.
3) Optional<T> 로 처리한다.
3)번의 방법은 null 이 아닌 T 타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다. 아무것도 담지 않은 옵셔널은 '비었다'라고 말한다. Optional은 원소를 최대 1개 가질 수 있는 '불변' 컬렉션이다. Optional<T> 가 Collection<T>를 구현하지는 않았지만, 원칙적으로는 그렇다.
Optional을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉬우며, null을 반환하는 메서드보다 오류 가능성이 작다.
Optional 포스팅 바로가기
https://devfunny.tistory.com/330
예제로 이해하기
1) 예외를 던진다.
package com.java.effective.item55;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
public class Main {
public static <E extends Comparable<E>> E max(Collection<E> c) {
/* 빈 컬렉션일 경우 에러를 던진다. */
if (c.isEmpty()) {
throw new IllegalArgumentException("빈 컬렉션");
}
E result = null;
for (E e : c) {
if (result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
return result;
}
}
2) Optional 을 반환한다.
package com.java.effective.item55;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
public class Main {
public static <E extends Comparable<E>> Optional<E> maxOptional(Collection<E> c) {
/* 빈 컬렉션일 경우 빈 Optional 을 반환한다. */
if (c.isEmpty()) {
return Optional.empty();
}
E result = null;
for (E e : c) {
if (result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
/* 값이 든 Optional 을 반환한다. */
return Optional.of(result);
/* Optional 을 사용하는 경우에는 null을 절대 반환하지 말자.
null 을 허용하는 Optional 을 반환하자. */
// return Optional.ofNullable(result);
}
}
3) 스트림의 종단 연산 중 상당수가 옵셔널을 반환한다. 스트림 버전을 보자.
package com.java.effective.item55;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
public class Main {
public static <E extends Comparable<E>> Optional<E> maxOptionalStream(Collection<E> c){
return c.stream().max(Comparator.naturalOrder());
}
}
▶ Stream.max()
Optional<T> max(Comparator<? super T> comparator);
Optional 반환 선택의 기준
옵셔널은 검사 예외와 취지가 비슷하다. 반환값이 없을 수도 있음을 클라이언트에게 명확히 알려준다. 비검사 예외를 던지거나 null을 반환한다면 API 사용자가 그 사실을 인지하지 못해서 예상치못한 에러가 발생할 수도 있다. 하지만 검사 예외를 던지면 클라이언트에서 반드시 이에 대처하는 코드를 작성해넣어야 한다.
클라이언트는 Optional 을 반환받고, 그 값의 여부에 따라 취할 행동을 선택해야한다.
1) 기본값 설정
package com.java.effective.item55;
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> words = new ArrayList<>();
words.add("A");
words.add("B");
words.add("C");
String lastWordInLexicon = maxOptional(words).orElse("단어 없음");
}
public static <E extends Comparable<E>> Optional<E> maxOptional(Collection<E> c) {
/* 빈 컬렉션일 경우 빈 Optional 을 반환한다. */
if (c.isEmpty()) {
return Optional.empty();
}
E result = null;
for (E e : c) {
if (result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
/* 값이 든 Optional 을 반환한다. */
return Optional.of(result);
/* Optional 을 사용하는 경우에는 null을 절대 반환하지 말자.
null 을 허용하는 Optional 을 반환하자. */
// return Optional.ofNullable(result);
}
}
2) 원하는 예외 던지기
package com.java.effective.item55;
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> words = new ArrayList<>();
words.add("A");
words.add("B");
words.add("C");
String lastWordInLexicon = maxOptional(words).orElseThrow(RuntimeException::new);
}
public static <E extends Comparable<E>> Optional<E> maxOptional(Collection<E> c) {
/* 빈 컬렉션일 경우 빈 Optional 을 반환한다. */
if (c.isEmpty()) {
return Optional.empty();
}
E result = null;
for (E e : c) {
if (result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
/* 값이 든 Optional 을 반환한다. */
return Optional.of(result);
/* Optional 을 사용하는 경우에는 null을 절대 반환하지 말자.
null 을 허용하는 Optional 을 반환하자. */
// return Optional.ofNullable(result);
}
}
▶ Optional.of()
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
▶ Optional.ofNullable()
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
-- empty()
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
3) 값이 있다고 가정하고 꺼내기
만약 꺼냈는데 값이 존재하지 않다면, NoSuchElementException 이 발생한다.
package com.java.effective.item55;
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> words = new ArrayList<>();
words.add("A");
words.add("B");
words.add("C");
String lastWordInLexicon = maxOptional(words).get();
}
...
}
▶ Optional.get()
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
4) Supplier<T>를 인수로 받는 orElseGet을 사용하여 값 초기 설정 비용을 낮출 수 있다.
▶ Optional.orElseGet()
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
5) isPresent() 메서드 사용하기
Optional<ProcessHandle> parentProcess = ph.parent();
System.out.println("부모 PID: " + (parentProcess.isPresent() ?
String.valueOf(parentProcess.get().pid()) : "N/A"));
/* 다듬은 코드 */
System.out.println("부모 PID: " +
ph.parent().map(h -> String.valueOf(h.pid()).orElse("N/A"));
- isPresent() : Optional이 채워져있을 경우 true, 비어있을 경우 false 를 반환한다.
- 스트림을 사용한다면 옵셔널들을 Stream<Optional<T>>로 받아서, 그 중 채워진 옵셔널들에서 값을 뽑아 Stream<T>에 건네 담아 처리하는 경우도 있다.
streamOfOptionals
.filter(Optional::isPresent)
.map(Optional::get)
자바 9에서 Optional에 stream() 메서드가 추가되었다. 이는 Optional 을 Stream으로 변환해주는 어댑터다. 옵셔널에 값이 있으면 그 값을 원소로 담은 스트림으로, 값이 없다면 빈 스트림으로 변환한다. 이를 Stream의 flatmap 메서드와 조합하면 앞의 코드를 다음처럼 명료하게 바꿀 수 있다.
streamOfOptionals.flatMap(Optional::stream)
flatMap 관련 포스팅 바로가기
https://devfunny.tistory.com/458
Optional 선택 상황
컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다. 빈 Optional<List<T>>를 반환하기보다는 빈 List<T>를 반환하는게 좋다. 빈 컨테이너를 그대로 반환하면 클라이언트에서 옵셔널 처리 코드를 넣지 않아도 된다.
즉, Optional<T>를 사용해야하는 상황은 아래와 같다.
1) 결과가 없을 수 있다.
2) 클라이언트가 이 상황을 특별하게 처리를 해야한다.
박싱된 기본 타입을 담는 옵셔널은 기본 타입 자체보다 무거울 수 밖에 없다. 자바 API는 int, long, double 전용 옵셔널 클래스도 제공한다. OptionalInt, OptionalLong, OptionalDouble이다. 이 옵셔널들도 Optional<T>가 제공하는 메서드를 거의 다 제공한다. 이렇게 대체재가 있으므로 박싱된 기본타입을 담은 옵셔널을 반환하면 안된다.
'Book > Effective Java' 카테고리의 다른 글
[교재 EffectiveJava] 아이템 57. 지역변수의 범위를 최소화하라 (0) | 2021.11.29 |
---|---|
[교재 EffectiveJava] 아이템 56. 공개된 API 요소에는 항상 문서화 주석을 작성하라 (0) | 2021.11.28 |
[교재 EffectiveJava] 아이템 54. null이 아닌 빈 컬렉션이나 배열을 반환하라 (0) | 2021.11.25 |
[교재 EffectiveJava] 아이템 53. 가변인수는 신중히 사용하라 (0) | 2021.11.24 |
[교재 EffectiveJava] 아이템 52. 다중정의는 신중히 사용하라 (0) | 2021.11.23 |