옵셔널
메서드가 특정 조건에서 값을 반환해낼 수 없을 때는 어떻게 해야 할까?
(자바8 이전)
- 예외를 던진다
- null을 반환
그러나 두 방법 모두 허점이 있다. 예외는 진짜 예외적인 상황에서만 사용해야 한다(Item69)
null을 반환하면 별도의 null처리 코드를 추가해야 하며, 어디선가 언젠가 뜬금없는 NullPointerException의 가능성도 있다.
(자바8 이후)
- Optional<T>
유효한 반환값이 없을 때는 Optional<T>를 반환하면, null이 아닌 T타입 참조를 하나 답거나, 혹은 아무것도 담지 않을 수 있다(즉, 빈 옵셔널) 옵셔널은 원소를 최대 1개 가질 수 있는 '불변' 컬렉션이다.
* Optional<T>가 Collection<T>를 구현하지는 않았지만, 원칙적으로 그렇다고 한다.
* Optional을 반환값 이외의 용도로 쓰는 경우는 매우 드물다.
─ 옵셔널 장점
- 옵셔널을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고, 사용하기 쉬우며, null 반환 메서드보다 오류 가능성이 작다.
- 옵셔널은 검사 예외와 취지가 비슷하다(Item71): 반환값 존재 여부를 API사용자에게 명확히 알려준다. 만약 비검사 예외를 던지거나 null을 반환한다면 API 사용자가 그 사실을 인지하지 못해 심각해질 수 있다. 하지만 검사 예욀르 던지면, 사용자는 이를 대처하는 코드를 반드시 작성해넣게 된다.
- 옵셔널을 반환한다면, 사용자는 값을 받지 못했을 때 취할 행동을 선택할 수 있다. 기본값을 넣거나, 예외를 던지거나 등.
─ 옵셔널 단점
- 옵셔널로 감싸면 안되는 것들이 있다! (내에서 확인)
- 옵셔널 반환에는 성능 저하가 뒤따른다. 성능에 민감하다면 null반환이나 예외던지기가 나을 수도 있다.
옵셔널 반환하기로 변경한 예시
컬렉션에서 최댓값을 구하는 예제다.
옵셔널 사용하지 않은 메서드를, 옵셔널 사용하도록 간단히 바꿔볼 것이다.
1. 옵셔널 사용X : 빈 컬렉션이 건네졌을 경우 예외 던지기
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;
}
2. 옵셔널 사용O : Optional<E>를 반환함
적절한 정적 팩터리를 사용해 옵셔널을 간단히 생성해주고 있다.
- 빈 옵셔널을 Optional.empty()로 만들었다.
- 값이 든 옵셔널은 Optional.of(value)로 생성했다.
└ value에 null을 넣으면 NullPointerException을 던지니 주의!
- null값도 허용하는 옵셔널을 만드려면, Optional.ofNullable(value)를 사용하면 된다.
public static <E extends Comparable<E>> Obtional<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);
}
스트림과 옵셔널
스트림의 종단 연산 중 상당수가 옵셔널을 반환한다.
스트림의 max연산을 예시로 보자면 이렇다.
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c){
return c.steam().max(Comparator.naturalOrder());
}
옵셔널은 검사 예외와 취지가 비슷하다(Item71)
옵셔널 반환값을 받는다면,
반환값이 있는지 없는지에 따라 명확히 대처하게 된다.
그래서 검사 예외와 취지가 비슷하다고 하는 것이다.
클라이언트가 옵셔널에서 값을 받지 못했을 때, 다음과 같은 행동들을 취할 수 있다.
1. 기본값 정해두기
String lastWordInLexicon = max(words).orElse("단어 없음...");
* 기본값을 설정하는 비용아 아주 커서 부담이 된다면, 여러 고급 메서드들을 고려해볼 수 있다. Supplier<T>를 인수로 받는 orElseGet, 또는 특별한 쓰임에 대한 filter, map, flatMap, ifPresent가 있다. API문서를 참조해 문제 해결을 검토해보자.
* isPresent 메서드도 참고해볼 수 있다. 그러나 isPresent를 사용한 코드 상당수는 앞서 언급한 메서드들을 통해 짧고+명확하게+용법에 맞도록 대체 가능하다.
예제: 부모 프로세스의 프로세스ID를 출력하거나, 부모가 없다면 "N/A"를 출력하는 코드
-- isPresent()를 사용한 예
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"));
-- 옵셔널을 Stream<Optional<T>>로 받아 Stream<T>에 건네 담아 처리하는 코드도 드물지 않다!
옵셔널에 값이 있다면, 그 값을 꺼내 스트림에 매핑한다.
streamOfOptionals
.filter(Optional::isPresent)
.map(Optinal::get)
-- 자바9에는 Optional에 stream() 메서드가 추가되었다.
옵셔널에 값이 있으면 그 값을 원소를 담을 스트림으로 변환하고, 값이 없으면 빈 스트림으로 변환한다.
이를 Stream의 flatMap과 조합하면 명료하게 아래처럼 구현 가능.
streamOfOptionals
.flatMap(Optional::stream)
2. 원하는 예외 던지기
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
3. 항상 값이 채워져 있다고 가정
Element lastNobleGas = max(Elements.NOBLE_BASES).get();
// 만약 값이 없으면 NoSuchElementException이 발생한다.
옵셔널을 사용하지 마라!
Optional 반환값을 언제 사용해야 할까? 기본적으로, "결과가 없을 수 있으며, 클라이언트가 이 상황을 처리해야 한다면" 사용한다.
무조건 좋은 게 아님을 명시하자.
컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안 된다.
즉, 빈 Optional<List<T>>를 반환하지 말고 빈 List<T>를 반환하는 것이 좋다(Item54)
그러면 클라이언트에 옵셔널 처리 코드를 넣지 않아도 되고 말이다.
Optional도 엄연히 새로 할당하고 초기화해야 하는 객체이다. Optional 객체에서 메서드를 호출해 사용하므로, 단계가 늘어난 셈이다. 그래서 성능을 고려하여 세심하게 사용해야 한다.
또한 "박싱된 기본타입"을 반환하는 옵셔널은 사용하지 말자. 박싱된 기본 타입을 담는 옵셔널은 기본 타입 자체보다 무거울 수밖에 없다. 자바에는 특별히 int, long, double 전용 옵셔널 클래스가 존재한다. OptionalInt, OptionalLong, OptionalDouble말이다. 필요하다면 이것들을 사용하도록 하고, 박싱된 기본타입 옵셔널은 지양하자.
'JAVA > Effective Java' 카테고리의 다른 글
[이펙티브 자바] 전통적인 for문보다는 for-each문을 사용하라 ─ 9장:일반적인 프로그래밍 원칙:Item58 (1) | 2023.10.19 |
---|---|
[이펙티브 자바] 지역변수의 범위를 최소화하라 ─ 9장:일반적인 프로그래밍 원칙:Item57 (0) | 2023.10.19 |
[이펙티브 자바] null이 아닌, 빈 컬렉션이나 배열을 반환하라 ─ 8장:메서드:Item54 (0) | 2023.10.18 |
[이펙티브 자바] 가변인수는 신중히 사용하라 ─ 8장:메서드:Item53 (0) | 2023.10.18 |
[이펙티브 자바] 다중정의(오버로딩)는 신중히 사용하라 ─ 8장:메서드:Item52 (1) | 2023.10.17 |