자바 8전에 메서드가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지
1️⃣ 던진다 예외
2️⃣ null 반환
위의 두 선택지의 문제점음
예외를 생성할 때에는 스택 추적 전체를 캡처하기 때문에 비용이 많이 든다.
그렇다고 null을 반환하자니 별도의 null 처리 코드를 추가해줘야 한다.
자바 8 이후, 더 나은 선택지가 생겼다.
👉 Optional <T>
null이 아닌 T 타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다.
옵셔널은 원소를 최대 1개 가질 수 있는 '불변' 컬렉션이다.(Collection <T>를 구현한 게 아님)
T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야 할 때
T 대신 Optional <T>를 반환하도록 선언하면 유효한 반환값이 없을 때는 빈 결과를 반환하는 메서드가 된다.
55-1) 컬렉션에서 최댓값을 구하기(컬렉션이 비어있으면 예외 던진다.)
public class MaxTest {
public static <E extends Comparable<E>> E max(Collection<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;
}
}
위 코드를 Optional <E>을 사용해 수정할 수 있다.
55-2) 컬렉션에서 최댓값을 구해 Optional <E>로 반환
public class MaxTest2 {
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);
}
}
return Optional.of(result);
}
}
정적 팩토리를 사용해 옵셔널을 생성해주기만 하면 된다.
빈 옵셔널은 Optional.empty(), 값이 든 옵셔널은 Optional.of(value)로 생성했다.
Optional.of(value)에 null을 넣으면 NullPointException을 던지는데,
null 값도 허용하는 옵셔널을 만들려면 Optional.ofNullable(value)를 사용하면 된다.
옵셔널을 반환하는 메서드에서 null을 반환하는 것은
옵셔널을 도입한 취지를 완전히 무시하는 행위이기 때문에 절대 null을 반환하지 말자!
옵셔널에 대해 알고 보니 null 반환이나 예외보다 그냥 옵셔널로 쓰는 게 장땡인 것 같은데
옵셔널 반환을 선택해야 하는 기준이 있을까? 🤔
옵셔널은 검사 예외랑 취지가 비슷한데,
(검사 예외를 던지면 클라이언트에서는 반드시 이에 대처하는 코드를 작성해야 함)
메서드가 옵셔널을 반환한다면 클라이언트는 값을 받지 못했을 때 취할 행동을 선택해야 한다.
👉 기본값 설정
55-4) 옵셔널 활용 1 - 기본값 정하기
String lastWordInLexicon = max(words).orElse("단어 없음..");
55-5) 옵셔널 활용 2 - 원하는 예외 던지기
다음 코드는 실제 예외가 아닌 예외 팩토리를 건넸다.
이렇게 하면 예외가 실제로 발생하지 않는 한 예외 생성 비용은 들지 않는다.
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
55-5) 옵셔널 활용 3 - 항상 값이 채워져 있다고 가정
옵셔널에 항상 값이 채워져 있다고 확신하면 곧바로 값을 꺼내 사용할 수도 있다.
잘못된 판단일 경우 NoSuchElementException이 발생
💡NoSuchElementException : 존재하지 않는 것을 가져오려고 할 때 발생하는 에러
Element lastNobleGas = max(Elements.NOBLE_GASES).get();
기본값을 설정할 때 비용이 크다면
Supplier <T>를 인수로 받는 orElseGet을 사용하면,
값이 처음 필요할 때 Supplier<T>를 사용해 생성해 초기 비용을 낮출 수 있다.
이외에 filter, map, flatMap, ifPresent 메서드가 있다.
✅ isPresent 메서드
옵셔널이 채워져 있으면 true, 비어 있으면 false 반환
Optional<ProcessHandle> parentProcess = ph.parent();
System.out.println("부모 PID:" + (parentProcess.isPresent() ?
String.valueOf(parentProcess.get().pid()) : "N/A"));
이 코드는 Optional의 map을 사용하여 아래와 같이 다듬을 수 있다.
✅ map
System.out.println("부모 PID:" +
ph.parent().map(h-> String.valueOf(h.pid))).orElse("N/A");
스트림을 사용한다면 옵셔널들을 Stream<Optional>로 받아서, 그중 채워진 옵셔널들에서 값을 뽑아 Stream에 건네 담아 처리하는 경우가 드물지 않다.
✅ filter
streamOfOptionals
.filter(Optional::isPresnet)
.map(Optional::get)
Optional에 값이 있다면, 그 값을 꺼내서 스트림에 매핑한다.
✅ flatMap
자바 9에서는 Optional에 stream() 메서드가 추가되었다.
이 메서드는 Optional을 Stream으로 변환해주는 어댑터다. 옵셔널에 값이 있으면 그 값을 원소로 담은 스트림으로,
값이 없다면 빈 스트림으로 변환한다.
streamOfOptionals
.flatMap(Optional::stream)
Optional을 사용하면 안되는 경우
- 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안 된다.
- 빈 Optional<List>를 반환하기보다는 빈 List를 반환하는 게 좋다. 빈 컨테이너를 그대로 반환하면 클라이언트에 옵셔널 처리 코드를 넣지 않아도 된다.
- 옵셔널을 맵의 값으로 사용하면 절대 안 된다. 맵 안에 키가 없다는 사실을 나타내는 방법이 두 가지가 있는데
하나는 키 자체가 없는 경우고, 다른 하나는 키는 있지만 그 키가 속이 빈 옵셔널인 경우다. 쓸데없이 복잡성만 높여서 혼란과 오류 가능성을 키울 뿐이다. - 옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 게 적절한 상황은 거의 없다.
Optional의 성능 이슈
- Optional도 엄연히 새로 할당하고 초기화해야 하는 객체이고, 그 안에서 값을 꺼내려면 메서드를 호출해야 하니 한 단계를 더 거치는 셈이다. 그래서 성능이 중요한 상황에서는 옵셔널이 맞지 않을 수 있다.
- 박싱 된 기본 타입을 담는 옵셔널은 기본 타입 자체보다 무거울 수밖에 없다. 값을 두 겹이나 감싸기 때문!
자바 API 설계자들은 int, long, double 전용 옵셔널 클래스이 있다.(OptionalInt, OptionalLong, OptionalDouble)
이렇게 대체제까지 있으니 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자.
정리
▶ 결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional를 반환하자!
▶ 옵셔널 반환에는 성능 저하가 있을 수 있기 때문에 성능에 민감한 메서드라면
null을 반환하거나 예외를 던지는 편이 나을 수 있다.
▶ 옵셔널을 반환값 이외의 용도로 쓰는 경우는 매우 드물다.
'🤓 스터디 > 이펙티브 자바' 카테고리의 다른 글
[아이템 69] 예외는 진짜 예외 상황에만 사용하라 (0) | 2023.04.22 |
---|---|
[아이템 56] 공개된 API 요소에는 항상 문서화 주석을 작성하라 (0) | 2023.04.21 |
[아이템 54] null이 아닌, 빈 컬렉션이나 배열을 반환하라 (0) | 2023.04.19 |
[아이템 53] 가변인수는 신중히 사용하라 (0) | 2023.04.18 |
[아이템 52] 다중정의는 신중히 사용하라 (0) | 2023.04.18 |
댓글