아이템55. 옵셔널 반환은 신중히 하라
자바8전에는 메스드가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지는 2가지였다. 1) 예외를 던지거나, 2) null을 반환하는 것. 두가지 허점이 존재한다. 예외는 진짜 예외적인 상황에서만 사용해야 하며, null을 반환하면 이런 문제가 생기지 않지만, 그 나름의 문제가 존재한다. null처리 코드를 추가해야 한다. null처리를 무시하고 반환된 null 값을 어딘가에 저장해 두면 언젠가 NullPointerException
이 발생한다.
자바8버전 이후 하나의 선택지가 추가됨. Optional<T>
는 null이 아닌 T타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다. 옵셔널은 원소를 최대 1개 가질수 있는 ‘불변’컬렉션이다.
옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자. 옵셔널을 도입한 취지를 완전히 무시하는 행위다.
그렇다면 null을 반환하거나 예외를 던지는 대신 옵셔널 반환을 선택해야 하는 기준은 무엇인가? 옵셔널은 검사 예외(Checked Exception)과 취지가 비슷하다. 반환값이 없을 수도 있음을 API사용자 에게 명확히 알려준다. 하지만 검사 예외를 던지면 클라이언트에서는 반드시 이에 대처하는 코드를 작성해 넣어야 한다. 비슷하게 메서드가 옵셔널을 반환한다면 클라이언트는 값을 받지 못했을 때 취할 행동을 선택해야 한다. 그중 하나는 기본값을 설정하는 방법이다.
옵셔널 활용1 - 기본값을 정해 둘 수 있다.
String word = max(words).orElse("단어없음");
예외를 생성할 때 스택 추적 전체를 캡쳐하므로 비용도 많이 발생함
옵셔널 활용2 - 원하는 예외를 던질 수 있다.
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
옵셔널 활용3 - 항상 값이 채워져 있다고 가정한다.
Element lastNobleGas = max(Elements.NOBLE_GASE).get();
여전히 적합한 메서드를 찾지 못했다면 isPresent메서드를 살펴보자. 이 메서드로 원하는 모든 작업을 수행할 수 있지만 신중히 사용해야 한다. 실제로 isPresent를 쓴 코드 중 상당수는 앞서 언급한 메서드들로 대체할 수 있으며, 그렇게 하면 더 짧고 명확하고 용법에 맞는 코드가 된다. 예를들면 부모 프로세스의 프로세스 ID를 출력하거나, 부모가 없다면 “N/A”를 출력하는 코드다.
Optional<ProcessHanlder> parentProcess = ph.process();
System.out.println(parentProcess.isPresent() ? String.valueOf(parentProcess.get().pid()): "N/A"));
이 코드를 Optional map을 사용하여 다음과 같이 바꿀 수 있다.
ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A");
스트림을 사용한다면 Optional을 Stream에 담아서 Stream<Optional<T>>
로 받아서 구현할 수 있다.
streamOfOptionals
.filter(Optional::isPresent)
.map(Optional::get)
반환값으로 무조건 옵셔널을 사용한다고 득이 되는 건 아니다. 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다. 빈 Optional<List<T>>
를 반환하기 보다는 빈 List<T>
를 반환하는게 좋다.
결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>
를 반환한다. 그런데 이렇게 하더라도 대가가 따른다. 옵셔널도 엄연히 새로 할당하고 초기화해야하는 객체이고, 그 안에서 값을 꺼내려면 메서드를 호출해야 하니 한 단계를 더 거치는 셈이다. 박싱된 기본 타입을 담는 옵셔널은 기본 타입 자체보다 무거울 수 밖에 없다. 값을 두겹이나 감싸기 때문, 그래서 자바 API설계자는 int, long, double 전용 옵셔널 클래스들을 준비했다. OptionalInt, OptionalLong, OptionalDouble이다.
다른 쓰임에 논하지 않았고, 그 이유는 적철치 않기 때문이다. 예컨대 옵셔널을 맵의 값으로 사용하면 절대 안됨. 그 이유는 맵 안에 키가 없다는 사실을 나타내는 방법이 2가지가 된다. 하나는 키 자체가 없는 것, 다른 하나는 키는 있지만 그 키가 속이 빈 옵셔널인경우다. 쓸데없이 복잡성만 높여서 혼란과 오류 가능성을 키울 뿐이다.
값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환값이 없을 가능성을 염두에 둬야 하는 메서드라면 옵셔널을 반환해야 할 상황일 수 있다. 하지만 옵셔널 반환에는 성능 저하가 뒤따르니, 성능에 민감한 메서드라면 null을 반환하거나 예외를 던지는 편이 나을 수 있다. 그리고 옵셔널을 반환값 이외의 용도로 쓴느 경우는 매우 드물다.