Item55 - 옵셔널 반환은 신중히 하라

2024. 1. 3. 20:20Book/이펙티브 자바

자바 8 전에는 메서드가 특정 조건에서 값을 반환할 수 없을 때

 1. 예외를 던지거나

 2. 객체 참조라면 null 값을 반환한다.

 

예외는 진짜 예외적인 상황에서만 사용해야 하며 null을 반환할 때는 null 처리 코드를 추가해야 한다.

 

자바 8 이후에는 다른 선택지가 생겼다.

Optinal<T>는 null이 아닌 T타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있는 불변 컬렉션이다.

 => (컬렉션을 구현한 건 아니다.)

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

 

이 메서드에 빈 컬렉션을 던지면 에러를 던진다. Optinal을 반환한 모습은 다음과 같다

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

 

적절한 정적 팩터리를 사용해 옵셔널을 생성해 반환했다. 빈 옵셔널은 Optional.empty()로 만들고, 값이 든 옵셔널은 Optional.of(value)를 사용하면 된다.

 

Optional.of(value)에서 null을 반환하면 에러가 발생한다. 이때는 Optional.ofNullable(value)를 사용할 수 있다. 하지만 옵셔널을 반환하는 메서드에서는 절대 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("단어 없음...");

 

API 사용자의 옵셔널 활용 2 - 에러

String lastWordInLexicon = max(words).orElseThrow(IllegalAccessError::new);

 

API 사용자의 옵셔널 활용 3 - 항상 값이 채워져 있다고 가정.

String lastWordInLexicon = max(words).get();

 

 

기본값을 설정하는 비용이 커서 부담이 되는 상황에서는 Supplier<T>를 인수로 받는 orElseGet을 사용하면 된다.

 

반환값으로 옵셔널을 사용한다고 해서 무조건 좋은건 아니다. 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다. List<T>를 반환하는데 빈 값은 옵셔널로 감싸기보다는 빈 List<T>를 반환하는 게 낫다. 빈 컨테이너를 반환하면 클라이언트에 옵셔널 처리 코드를 넣지 않을 수 있다.