메서드가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지는 무엇이 있을까?
[ 자바 8 이전 ]
- 예외 던지기
- 진짜 예외 사항에서 사용하고 있는지 생각해라.
- 예외를 생성할 때 스택 추적 전체를 캡쳐해야 하므로 비용이 많이 든다.
- null 반환하기
- null처리 코드를 별도로 추가해야 한다.
- null처리를 무시하고 반환된 null값을 어딘가에 저장하면 언젠가 NullPointerException이 발생할 수 있다.
[ 자바 8 이후 ]
- Optional<T>
- 원소를 최대 1개 가질 수 있는 '불변' 컬렉션
- null이 아닌 T 타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다.
- 아무것도 담지 않은 optional은 '비었다'고 하고, 무엇인가 담은 옵셔널은 '비지 않았다'라고 한다.
- 보통은 T를 반환하지만, 특정 조건에서는 아무 것도 반환해야하지 않을 경우에 사용.
Optional를 반환하도록 선언하면 된다.
유효한 반환값이 없을 때는 빈 결과를 반환하는 메소드가 만들어진다.
옵셔널을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉬우며, null을 반환하지 않으므로 오류가 날 가능성도 적어진다.
그런 사람이 있으면 안되겠지만 Optional에 null을 반환하는 이상한 행동은 절대 하지 말자.
hoxy 자바 8 이상을 쓰고 있는가? 그럼 optional을 써라.
왜? 사용자는 null 이 반환되는지, 예외가 발생하는지 알 수 없다. 반환 값이 없을 수도 있다는 것 또한 알 수 있기 때문이다.
Optional은 검사 예외와 취지가 비슷하다. 즉 반환 값이 없을 수 도 있다는것을 사용자에게 명확히 알려준다.
이는 비검사 예외 또는 null을 반환하는 것과 달라, 사용자가 그 사실을 인지하지 못해 이상한 결과가 나올 수 있는 것을 막아준다.
그럼 메서드가 옵셔널을 반환했다고 치자. 클라이언트는 값을 받지 못했을 때 어떻게 해야할까?
1. 상황에 맞는 예외를 던질 수 있다.
String lastWordInLexicon = max(words).orElse("단어 없음...");
특히 이렇게 하면 실제 예외가 아니라 예외 팩터리를 건냈으므로, 예외 생성 비용이 들지 않는다.
2. 원하는 예외를 던질 수 있다.
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
옵셔널에 항상 값이 채워져 있다 확신하면 곧바로 값을 꺼내 선택하면 된다.
3. 항상 값이 채워져 있다고 가정한다.
Element lastNobleGas = max(Elements.NOBLE_GASES).get();
4. 기본값 설정 비용이 클 경우 Supplier<T>를 인수로 받는 orElseGet을 사용한다.
5. 적합한 메서드를 찾지 못했을 경우 isPresent 메서드를 살펴보자.
안전 밸브 역할로, optional이 채워져 있으면 true, 비어있으면 false를 반환한다.
public class ParentPid {
public static void main(String[] args) {
ProcessHandle ph = ProcessHandle.current();
// Inappropriate use of isPresent
Optional<ProcessHandle> parentProcess = ph.parent();
System.out.println("Parent PID: " + (parentProcess.isPresent() ?
String.valueOf(parentProcess.get().pid()) : "N/A"));
// Equivalent (and superior) code using orElse
System.out.println("Parent PID: " +
ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));
}
}
원하는 모든 메서드를 수행할 수 있으나 신중히 사용하자. isPresent를 사용하기 전에 앞의 옵셔널 활용 1~4로 표현할 수 있는지 면밀히 검토하자. 그 편이 더 짧고 명확하며 용법에 맞는 코드이다.
[ 자바 9 이후 ]
stream() 메서드가 추가되었다. 이는 optional을 Stream으로 변환해주는 어댑터다.
값이 있으면 값을 담은 스트림을, 없으면 빈 스트림으로 변환한다.
옵셔널을 사용하면 안되는 경우
- 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입
- 빈 Optional<List<T>>를 반환하기보다는 빈 List<T>를 반환하는 게 좋다.
- 빈 컨테이너를 그대로 반환하면 클라이언트에 옵셔널 처리 코드를 넣지 않아도 된다.
- 박싱된 기본 타입을 옵셔널에 담는 것은 값을 두 겹 감싸는 것으로 무거울 수 있다.
- 따라서 int, long, double 전용 옵셔널 클래스가 존재한다.
- Optional<T>가 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 절대 없게 하자.
- 옵셔널을 맵의 값으로 사용하면 절대 안된다.
- 맵 안에 키가 없다는 사실을 나타내는 방법이 두가지가 된다.
- 쓸데없이 복잡하고 오류 가능성이 높아진다.
- 옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 게 적절한 상황은 거의 없다.
옵셔널을 사용하면 좋은 경우
- 결과를 알 수 없으며, 클라이언트가 이 상황을 특별하게 처리해야 하는 경우
- 단, Optional을 사용하면 새로 할당하고 객체를 초기화하며 값을 꺼내기 위해 메서드를 호출하는 한 단계를 더 거치기 때문에 성능이 저하될 수 있다. (따라서 성능이 중요한 상황에는 안맞을 수도 있다.)
핵심 정리
값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환 값이 없을 가능성을 염두에 둬야 하는 메서드라면 옵셔널을 반환해야할 상황일 수 있다.
하지만 옵셔널 반환에는 성능 저하가 뒤따르니, 성능에 민감한 메서드라면 null을 반환하거나 예외를 던지는 편이 나을 수도 있다.
옵셔널을 반환값 이외의 용도로 쓰는 경우는 매우 드물다.
'JAVA > Effective Java' 카테고리의 다른 글
공지 (0) | 2022.07.30 |
---|---|
item 56. 공개 API 요소에는 항상 문서화 주석을 작성하라 (0) | 2022.07.14 |
item 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라 (0) | 2022.07.14 |
item 53. 가변인수는 신중히 사용하라 (0) | 2022.07.13 |
item 52. 다중 정의는 신중히 사용해라 (0) | 2022.07.10 |
댓글