기록이 힘이다.

[이펙티브 자바]아이템55. 옵셔널 반환은 신중히 하라 본문

JAVA

[이펙티브 자바]아이템55. 옵셔널 반환은 신중히 하라

dev22 2023. 4. 10. 22:07
728x90

자바 8 전에는 메서드가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지가 두 가지 있었다. 예외를 던지거나, (반환 타입이 객체 참조라면) null을 반환하는 것이다.

자바 버전이 8로 올라가면서 또 하나의 선택지가 생겼다. 그 주인공인 Optional<T>는 null이 아닌 T타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다. 옵셔널은 원소를 최대 1개 가질 수 있는 ‘불변’ 컬렉션이다.

보통은 T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야 할 때 T 대신 Optional<T>를 반환하도록 선언하면 된다. 옵셔널을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉬우며, null을 반환하는 메서드보다 오류 가능성이 작다.

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);
    }

옵셔널을 반환하는 메서드에서는 절대 Null을 반환하지 말자. 옵셔널을 도입한 취지를 완전히 무시하는 행위다.

public static <E extends Comparable<E>>
    Optional<E> max(Collection<E> c) {
        return c.stream().max(Comparator.naturalOrder());
    }

그렇다면 null을 반환하거나 예외를 던지는 대신 옵셔널 반환을 선택해야 하는 기준은 무엇인가?

옵셔널은 검사 예외와 취지가 비슷하다. 즉, 반환 값이 없을 수도 있음을 API 사용자에게 명확히 알려준다.

  1. 기본값을 정해둘 수 있다.
String lastWordInLexicon = max(words).orElse("단어 없음...");
  1. 원하는 예외를 던질 수 있다. =⇒ 예외 생성 비용은 들지 않는다.
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
  1. 항상 값이 채워져 있다고 가정한다. =⇒ 아닐시, NoSuchElementException 발생
Element lastNobleGas = max(Elements.NOBLE_GASES).get();

기본값을 설정하는 비용이 아주 커서 부담이 될 때가 있다. Supplier<T>를 인수로 받는 orElseGet, filter, map, flatMap, ifPresent가 있다.

여전히 적합한 메서드를 찾지 못했다면 isPresent 메서드를 살펴보자. 안전밸브 역할의 메서드로, 옵셔널이 채워져 있으면 true를, 비어 있으면 false를 반환한다.

streamOfOptionals
	.filter(Optional::isPresent) 옵셔널에 값이 있다면
		.map(Optional::get) 그 값을 꺼내 스트림에 매핑한다.

자바 9에서는 Optional에 stream() 메서드가 추가되었다. 이 메서드는 Optional을 stream으로 변환해주는 어댑터다.

streamOfOptionals
	.flatMap(Optional::stream)

반환값으로 옵셔널을 사용한다고 해서 무조건 득이 되는 것은 아니다. 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다.

빈 Optional<List<T>>보다는 빈 List<T>를 반환하는 게 좋다.

기본규칙

결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>를 반환한다.

박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자.

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

옵셔널 반환에는 성능 저하가 뒤따르니, 성능에 민감한 메서드라면 null을 반환하거나 예외를 던지는 편이 나을 수 있다.