Search

Item 55. 옵셔널 반환은 신중히 하라

생성일
2023/08/01 05:57
챕터
8장 - 메서드

요약

메서드에서 값을 반환할 때, 값이 없을 수도 있으면 null을 사용하지말고 Optional을 사용하자.
스트림에도 옵셔널이 있으니 잘 활용하자.
옵셔널에서 제공되는 여러 메서드들을 활용하자.
int, long, double의 경우 자바에서 제공하는 박싱된 옵셔널 타입을 사용하자.
컬렉션의 키와 값, 원소, 배열의 원소로 옵셔널을 사용하지말자.

옵셔널

자바 8 이전에는 메서드가 반환 값이 없을 때 예외를 던지거나 null을 반환하는 두 가지 선택지 밖에 없었다.
예외를 던지는 것은 진짜 예외적인 상황에서 사용되어야 하고, 예외를 발생시킬 때 스택 추적 전체를 캡처하기 때문에 비용도 적지 않다.
null을 반환하는 경우에는 null이 반환될 수 있는 가능성을 고려해 null을 처리할 수 있는 방어적 코드를 작성해야 한다. 또한 실수로 빼먹고 처리를 하지 않는다면 NullPointerException이 발생할 수 있다.
public static <E extends Comparable<E>> E max(Colletion<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; }
Java
복사
자바 8에서는 이런 상황에서 Optional<T>로 타입 참조를 담고 있거나, 아무것도 담기지 않을 수 있게 되었다.
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.resquireNonNull(e); } return Optional.of(result); }
Java
복사
이와 같이 값이 없다면 빈 옵셔널을 반환하는 코드를 작성할 수 있고, 주의해야할 점은 옵셔널을 반환하는 메서드에서는 null을 반환하면 안된다는 것이다.

스트림과 옵셔널

자바 8부터 도입된 스트림에도 옵셔널이 도입되었는데, 이를 이용해 위 코드를 간결하게 줄일 수 있다.
public static <E extends Comparable<E>> Optional<E> max(Collections<E> c) { return c.stream().max(Comparator.naturalOrder()); }
Java
복사
옵셔널에는 isPresent라는 메서드를 통해 값이 존재하는지 확인할 수 있다.
Optional<ProcessHandle> parentProcess = ph.parent(); System.out.println("부모 PID: " + (parentProcess.isPresent() ? parentProcess.get().pid() : "N/A"));
Java
복사
이 코드는 Optional의 map을 사용하여 다듬을 수 있다.
System.out.println("부모 PID: " + ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));
Java
복사
여기에 스트림을 적용하여 값이 있는 것들만 뽑아 처리하도록 할 수 있다.
streamOfOptionals .filter(Optional::isPresent) .map(Optional::get)
Java
복사

옵셔널 활용

옵셔널에는 기본값을 설정하여 null인 경우 null 대신 기본값을 넣을 수 있다.
String lastWordInLexicon = max(words).orElse("단어 없음");
Java
복사
옵셔널의 값이 null인 경우에 예외 처리를 하고 싶다면, 예외를 던지도록 할 수 있다.
String lastWordInLexicon = max(words).orElseThrow(Exception::new);
Java
복사
옵셔널에 값이 항상 채워져 있음을 확신할 수 있다면, 값을 꺼내어 사용할 수 있다.
String lastWordInLexicon = max(words).get();
Java
복사

박싱된 타입의 옵셔널

박싱된 타입을 옵셔널로 사용하는 것은 2번 감싸서 사용하기 때문에 기본 타입 자체보다 무거울 수 밖에 없다.
때문에 int, long, double을 옵셔널에 사용하고 싶으면 Integer, Long, Double로 박싱하여 옵셔널에 넣지 말고, 자바에서 만들어둔 OptionalInt, OptionalLong, OptionalDouble 클래스를 사용하자.
잘 사용되지 않거나 덜 중요한 타입인 Boolean, byte, Character, Short, Float은 예외적으로 그냥 사용한다.

컬렉션과 옵셔널

만약 옵셔널을 Map 컬렉션과 같이 사용하면, 키 자체가 없는 경우와 키가 있지만 속이 빈 옵셔널인 경우로 나뉘어 로직이 불필요하게 복잡해지고 오류 가능성을 높인다.
옵셔널을 컬렉션의 key, value, 원소나 배열의 원소로 사용하지 말자.