메서드가 값을 반환할 수 없을 때
1. 예외를 던짐
- 진짜 예외적인 상황에서만 사용해야 한다.
- 예외를 생성할 때 스택 추적 전체를 캡처하므로 비용도 많이 든다.
2. 반환 타입이 객체 참조라면 null을 반환
- null을 반환할 수 있는 메서드를 호출할 때는, 별도의 null 처리 코드를 추가해야 한다.
- null 처리를 무시하고 반환된 null 값을 어딘가에 저장해 두면 언젠간 NullPointerException이 발생할 수 있다. (실제 원인과는 전혀 상관없는 코드에서)
3. Optional <T>
- 자바 8 이후 Optional <T>는 null이 아닌 T타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다.
- 아무것도 담지 않은 옵셔널을 '비었다'라고 말한다.
- 어떤 값을 담은 옵셔널을 '비지 않았다'라고 한다.
- 옵셔널은 원소를 최대 1개 가질 수 있는 '불변' 컬렉션이다.
Optional <T>
- 보통 T를 반환한다.
- 특정 조건에서 아무것도 반환하지 않아야 할 때 T 대신 Optional <T>를 반환하도록 선언하면 유효한 반환값이 없을 때는 빈결과를 반환하는 메서드가 만들어진다.
- 이는 예외를 던지는 메서드보다 유연하고 사용하기 쉽고, NULL을 반환하는 메서드 보다 오류 가능성이 적다.
최댓값을 구하는 max 메서드 - 빈 컬렉션이면 예외를 던진다.
최댓값을 구하는 max 메서드 - Optional <E>로 반환한다.
- 적절한 정적 팩터리를 사용해 옵셔널을 생성한다.
- 빈 옵셔널은 Optional.empty()로 만들고, 값이 든 옵셔널은 Optional.of(value)로 생성한다.
Optional.of(value)에 null을 넣으면 NullPointerException을 던지니 주의하자.
옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자.
스트림 버전
- 스트림의 종단 연산 중 상당수가 옵셔널을 반환한다.
null을 반환하거나 예외를 던지는 대신 옵셔널 반환을 선택해야 하는 기준
옵셔널은 검사 예외와 취지가 비슷하다.
- 반환값이 없을 수도 있음을 API 사용자에게 명확히 알려준다.
- 비검사 예외를 던지거나 null을 반환한다면 API 사용자가 그 사실을 인지하지 못해 좋지 않은 결과로 이어질 수 있다.
- 메서드가 옵셔널을 반환한다면 클라이언트는 값을 받지 못했을 때 취할 행동을 선택해야 한다.
기본값을 설정한다.
String lastWordInLexicon = max(words).orElse("단어 없음...");
- 기본값을 설정하는 비용이 커서 부담이 될 때 Supplier <T>를 인수로 받는 orElseGet을 사용하면 값이 처음 필요할 때 Supplier<T>를 사용해 생성하므로 초기 설정 비용을 낮출 수 있다.
원하는 예외를 던질 수 있다.
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
- 상황에 맞는 예외를 던질 수 있다.
- 실제 예외가 아니라 예외 팩터리를 건네서 예외 생성 비용이 들지 않는다.
항상 값이 채워져 있다고 가정한다.
Element lastNobleGas = max(Elements.NOBLE_GASES).get();
- 옵셔널에 항상 값이 채워져 있다고 확신하면 그냥 곧바로 값을 꺼내 사용한다.
- 잘못 판단한 것이라면 NoSuchElementException이 발생한다.
isPresent
- isPresent 메서드는 안전밸브 역할의 메서드로, 옵셔널이 채워져 있으면 true를, 비어 있으면 false를 반환한다.
- 이 메서드로는 원하는 모든 작업을 수행할 수 있지만 신중히 사용해야 한다.
- isPresent를 쓴 코드 중 상당수는 fileter, map, flatMap 메서드들로 대체할 수 있고, 그렇게 하면 더 짧고 명확한 코드가 된다.
부모 프로세스의 프로세스 ID를 출력하거나, 부모가 없다면 "N/A"를 출력하는 코드 - 불필요하게 사용한 isPresent 제거
- Optional의 map을 사용하여 개선
옵셔널 사용 시 주의할 점
반환값으로 옵셔널을 사용하는 게 무조건 득이 되는 건 아니다.
컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안 된다.
- 빈 Optional <List <T>>를 반환하기보다는 빈 List <T>를 반환하는 게 좋다.
- 빈 컨테이너를 그대로 반환하면 클라이언트에 옵셔널 처리 코드를 넣지 않아도 된다.
결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional <T>를 반환하자.
- Optional<T>를 반환하는 데는 대가가 따른다.
- Optional도 새로 할당하고 초기화해야 하는 객체이고, 그 안에서 값을 꺼내려면 메서드를 호출해야 하니 한 단계를 더 거치는 셈이다.
- 성능이 중요한 상황에서는 옵셔널이 맞지 않을 수 있다.
박싱 된 기본 타입을 담는 옵셔널을 반환하지 말자.
- 박싱된 기본 타입을 담는 옵셔널은 값을 두 번 감싸므로 기본 타입 자체보다 무겁다.
- int, long, double 전용 옵셔널 클래스 OptionalInt, OptionalLong, OptionalDouble이 있다.
- 해당 메서드들은 Optional <T>가 제공하는 메서드를 거의 다 제공한다.
옵셔널을 반환하고 반환된 옵셔널을 처리하는 것 이외는 적절치 않은 쓰임이다.
- 옵셔널을 맵의 값으로 쓰면 맵 안에 키가 없다는 사실을 나타내는 방법이 두 가지가 된다.
- 하나는 키 자체가 없는 경우고, 다른 하나는 키는 있지만 그 키가 속이 빈 옵셔널인 경우다.
- 옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 게 적절한 상황은 거의 없다.
옵셔널을 인스턴스 필드에 저장하는 경우
- 만약 인스턴스의 필드 중 상당수는 필수가 아니고, 그 필드들이 기본 타입이라 값이 없음을 나타낼 방법이 마당치 않다면 선택적 필드의 getter 메서드들이 옵셔널을 반환하게 하면 좋다.
- 이럴 때는 필드 자체를 옵셔널로 선언하는 것도 좋은 방법이다.
결론
- 값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환값이 없을 가능성을 염두에 둬야 하는 메서드라면 옵셔널을 반환해야 할 상황일 수 있다.
- 하지만 옵셔널 반환에는 성능 저하가 뒤따르니, 성능에 민감한 메서드라면 null을 반환하거나 예외를 던지는 게 나을 수 있다.
- 옵셔널을 반환값 이외의 용도로 쓰는 경우는 매우 드물다.
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] Item 57. 지역변수의 범위를 최소화하라 (0) | 2023.07.22 |
---|---|
[Effective Java] Item 56. 공개된 API 요소에는 항상 문서화 주석을 작성하라 (0) | 2023.07.22 |
[Effective Java] Item 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라 (0) | 2023.07.16 |
[Effective Java] Item 53. 가변인수는 신중히 사용하라 (0) | 2023.07.16 |
[Effective Java] Item 51. 메서드 시그니처를 신중히 설계하라 (1) | 2023.07.16 |