핵심 정리
값을 반환하지 못할 가능성이 있고, 호출 할 때마다 반환 값이 없을 가능성을 염두에 둬야하는 메서드라면 옵셔널을 반환해야 할 상황일 수 도 있다. 하지만 옵셔널 반환에는 성능 저하가 뒤따르니, 성능에 민감한 메서드라면 null을 반환하거나 예외를 던지는 편이 나을 수 있다. 그리고 옵셔널을 반환값 이외의 용도로 쓰는 경우는 매우 드물다.
메서드가 특정 조건에서 값을 반환할 수 없을 때, 취할 수 있는 선택지
1. 예외를 던진다.
하지만, 예외는 진짜 예외적인 상황에서만 사용해야 하며 예외를 생성할 때 스택 추적 전체를 캡처하므로 비용이 만만치 않다.
2. null을 반환한다.
null을 반환할 수 있는 메서드를 호출할 때는, 별도의 null 처리 코드를 추가해야 한다.
3. Optional<T>를 반환한다.
Optional<T>는 null이 아닌 T 타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다. 아무것도 담지 않은 옵셔널은 '비었다'고 말한다. 반대로 어떤 값을 담은 옵셔널은 '비지 않았다'고 한다.
옵셔널은 원소를 최대 1개 가질 수 있는 '불변' 컬렉션이다.
Optional<T> 반환 예시
1) 컬렉션
// Run Configuration -> program arguement 설정
public static void main(String[] args) {
List<String> words = Arrays.asList(args);
System.out.println(max(words));
}
// 컬렉션에서 최댓값을 구해 Optional<E>로 반환한다.
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);
}
}
//값이 든 옵셔널 생성
//Optional.of(value)에 null을 넣으면 NullPointerException을 던지니 주의하자.
return Optional.of(result);
}
null 값도 허용하는 옵셔널을 만들려면 Optional.ofNullable(value)를 사용하면 된다. 옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자. 옵셔널을 도입한 취지를 완전히 무시하는 행위이다.
2) 스트림 버전
// 컬렉션에서 최댓값을 구해 Optional<E>로 반환한다.(Stream)
public static <E extends Comparable<E>> Optional<E> max2(Collection<E> c){
return c.stream().max(Comparator.naturalOrder());
}
스트림의 종단 연산 중 상당수가 옵셔널을 반환한다.
그렇다면 옵셔널 반환을 선택해야 하는 기준은 무엇인가?
옵셔널은 검사 예외와 취지가 비슷하다. 즉, 반환 값이 없을 수도 있음을 API 사용자에게 명확히 알려준다. 비검사 예외를 던지거나 null을 반환한다면 API 사용자가 그 사실을 인지하지 못해 끔찍한 결과로 이어질 수 있다. 하지만 검사 예외를 던지면 클라이언트에서는 반드시 이에 대처하는 코드를 작성해 넣어야 한다.
클라이언트가 값을 받지 못했을 때 취할 행동(옵셔널 활용)
1)기본 값을 정해둘 수 있다.
String lastWordInLexicon = max2(words).orElse("단어 없음...");
2)원하는 예외를 던질 수 있다.
Toy myToy = max2(toys).orElseThrow(TemperTantrumException::new);
실제 예외가 아니라 예외 팩토리를 건넨 것에 주목하자. 이렇게 하면 예외가 실제로 발생하지 않는 한 예외 생성 비용은 들지 않는다.
3) 항상 값이 채워져 있다고 가정한다.
Element lastNobleGas = max2(Elements.NOBLE_GASES).get();
옵셔널에 항상 값이 채워져 있다고 확신한다면 그냥 곧바로 값을 꺼내 사용하는 선택지도 있다. 다만 잘못 판단한 것이라면 NoSuchElementException이 발생할 것이다.
또 다른 방법 isPresent
부모 프로세스의 프로세스 ID를 출력하거나, 부모가 없다면 "N/A"를 출력하는 코드다.
Optional<ProcessHandle> parentProcess = ph.parent();
System.out.println("부모 PID" + (parentProcess.isPresent() ?
String.valueOf(parentProcess.get().pid()) : "N/A"));
Optional의 map을 사용하여 다음처럼 다듬을 수 있다.
System.out.println("부모 PID" + (ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A")));
Optional에 값이 있다면(Optional::isPresent) 그 값을 꺼내(Optional::get) 스트림에 매핑한다.
// 자바8 구현
streamOfOptionals
.filter(Optional::isPresent)
.map(Optional::get)
옵셔널에 값이 있으면 그 값을 원소로 담은 스트림으로, 값이 없다면 빈 스트림으로 변환한다.
//자바 9에서 Optional에 stream() 메서드가 추가되었다.
//이 메서드는 Optional을 Stream으로 변환해주는 어댑터다.
streamOfOptionals
.flatMap(Optional::stream)
어떤 경우에 메서드 반환 타입을 T 대신 Optional<T>로 선언해야 할까?
결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>를 반환한다.
Optional<T>를 반환하면 안되는 경우
1. 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안 된다.
빈 Optional<List<T>>를 반환하기보다는 빈 List<T>를 반환하는게 좋다. 빈 컨테이너를 그대로 반환하면 클라이언트에서 옵셔널 처리 코드를 넣지 않아도 된다.
2. 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자.
박싱된 기본 타입을 담는 옵셔널은 기본 타입 자체보다 무거울 수 밖에 없다. int, long, double은 전용 옵셔널 클래스인 OptionalInt, OptionalLong, OptionalDouble을 사용해라.
단, '덜 중요한 기본 타입'용인 Boolean, Byte, Character, Short, Float은 예외일 수 있다.
3. 옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는게 적절한 상황은 거의 없다
'일상 > 레벨업 독서' 카테고리의 다른 글
[이펙티브 자바] Item 61 박싱된 기본 타입보다는 기본 타입을 사용하라. (0) | 2022.05.17 |
---|---|
[이펙티브 자바] Item 60 정확한 답이 필요하다면 float와 double은 피하라. (0) | 2022.05.16 |
[이펙티브 자바] Item 54 null이 아닌, 빈 컬렉션이나 배열을 반환하라 (0) | 2022.05.03 |
[이펙티브 자바] Item 49 매개변수가 유효한지 검사하라 (0) | 2022.05.02 |
[이펙티브 자바] Item 48 스트림 병렬화는 주의해서 적용하라 (0) | 2022.05.02 |