Java8 In Action - 8. null 대신 Optional


Java8 In Action 을 읽고 정리한 내용이다. java.util.Optional<T>에 대해서 설명한다.

Optional 적용 패턴

Optional 객체 만들기

Optional을 사용하려면 Optional 객체를 만들어야 한다. 다양한 방법으로 Optional 객체를 만들 수 있다.

빈 Optional

정적 팩토리 메서드 Optional.empty로 빈 Optional 객체를 얻을 수 있다.

1
Optional<Car> optionalCar = Optional.empty();

null이 아닌 값으로 Optional 만들기

정적 팩토리 메서드 Optional.of로 null이 아닌 값을 포함하는 Optional을 만들 수 있다.

1
Optional<Car> optionalCar1 = Optional.of(car);

carnull이면 즉시 NullPointerException이 발생한다.

null값으로 Optional 만들기

정적 팩토리 메서드 Optional.ofNullable로 null값을 저장할 수 있는 Optional을 만들 수 있다.

1
Optional<Car> optionalCar2 = Optional.ofNullable(car);

carnull이면 빈 Optional(Optional.empty) 객체가 반환된다.

디폴트 액션과 Optional 언랩

Optional클래스는 Optional 인스턴스에서 값을 읽을 수 있는 다양한 인스턴스 메서드를 제공한다.

  1. get()은 값을 읽는 가장 간단한 메서드면서 동시에 가장 안전하지 않은 메서드다. 메서드 get은 래핑된 값이 있으면 해당 값을 반환하고 값이 없으면 NoSuchElementException을 발생 시킨다. 따라서 Optional 에 값이 반드시 있다고 가정할 수 있는 상황이 아니면 get 메서드를 사용하지 않는 것이 바람직하다.
  2. orElse(T other) 메서드를 이용하면 Optional이 값을 포함하지 않을 때 디폴트값으로 제공할 수 있다.
  3. orElseGet(Supplier<? extends T> other)orElse 메서드에 대응하는 게으른 버전의 메서드가. Optional에 값이 없을 때만 Supplier가 실행되기 때문이다.
  4. orElseThrow(Supplier<? extends X> exceptionSupplier)Optional이 비어있을때 예외를 발생시킨다는 점에서 get 메서드와 비슷하다. 하지만 이 메서드는 발생시킬 예외의 종류를 선택할 수 있다.
  5. ifPresent(Comsumer<? super T> comsumer)를 이용하면 값이 존재할 때 인수로 넘겨준 동작을 실행할 수 있다. 값이 없으면 아무 일도 일어나지 않는다.

Optional 클래스의 메서드

메서드 설명
empty 빈 Optional 인스턴스 반환.
filter 값이 존재하며 프레디케이트와 일치하면 값을 포함하는 Optional을 반환하고, 값이 없거나 프레디케이트와 일치하지 않으면 빈 Optional을 반환함.
flatMap 값이 존재하면 인수로 제공된 함수를 적용한 결과 Optional을 반환하고, 값이 없으면 빈 Optional을 반환함.
get 값이 존재하면 Optional이 감싸고 있는 값을 반환하고, 값이 없으면 NoSuchElementException이 발생함.
ifPresent 값이 존재하면 지정된 Consumer를 실행하고, 값이 없으면 아무 일도 일어나지 않음.
isPresent 값이 존재하면 true를 반환하고, 값이 없으면 false를 반환함.
map 값이 존재하면 제공된 매핑 함수를 적용함.
of 값이 존재하면 값을 감싸는 Optional을 반환하고, 값이 null이면 NullPointerException을 발생함.
ofNullable 값이 존재하면 값을 감싸는 Optional을 반환하고, 값이 null이면 빈 Optional을 반환함.
orElse 값이 존재하면 값을 반환하고, 값이 없으면 디폴트값을 반환함.
orElseGet 값이 존재하면 값을 반환하고, 값이 없으면 Supplier에서 제공하는 값을 반환함.
orElseThrow 값이 존재하면 값을 반환하고, 값이 없으면 Supplier에서 생성한 예외를 발생함.

Optional을 사용한 실용 예제

잠재적으로 null이 될 수 있는 대상을 Optional로 감싸기

Map 클래스의 get 메서드의 시그너처는 우리가 고칠 수 없지만 get 메서드의 반환값은 Optional로 감쌀 수 있다.

1
2
3
4
5
// Before
Object value = map.get("key");

// After
Optional<Object> value = Optional.ofNullable(map.get("key"));

예외의 Optional

문자열을 정수 Optional로 변환

1
2
3
4
5
6
7
public static Optional<Integer> stringToInt(String s) {
try {
return Optional.of(Integer.parseInt(s)); // 문자열을 정수로 변환할 수 잇으면 정수로 변환된 값을 포함하는 Optional을 반환한다.
} catch (NumberFormatException e) {
return Optional.empty(); // 그렇지 않으면 빈 Optiona을 반환한다.
}
}

응용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Properties props = new Properties();
props.setProperty("a", "5");
props.setProperty("b", "true");
props.setProperty("c", "-3");

// Before
public static int readDurationImperative(Properties props, String name) {
String value = props.getProperty(name);
if (value != null) {
try {
int i = Integer.parseInt(value);
if (i > 0) {
return i;
}
} catch (NumberFormatException nfe) { }
}
return 0;
}

// After
public static int readDurationWithOptional(Properties props, String name) {
return Optional.ofNullable(props.getProperty(name))
.flatMap(s -> stringToInt(s))
.filter(i -> i > 0).orElse(0);
}

소스코드

참조