Optional의 등장배경
NullPointerException 은 Java 로 개발을 하면서 가장 많이 겪는 예외 중 하나일 것이다.
우리는 이 NullPointerException 을 피하기위해 다음과 같은 코드를 짜곤한다.
public String getCarName(Person person) {
if (person != null) {
Car car = person.getCar();
if (car != null) {
return car.getName();
}
}
}
혹은 다음과 같은 방법도 있다.
public String getCarName(Person person) {
if (person == null) {
return "Unknown";
}
Car car = person.getCar();
if(car == null) {
return "Unknown";
}
return car.getName();
}
첫번째 방식을 깊은 의심, 두번째 방식을 너무 많은 출구 라고 표현하기도 한다.
하지만 위 두 방식은 코드를 어지럽힌다. 때로는 null 체크를 놓칠수도 있다.(에러가 발생된다)
이와 같은 상황에서 Optional 이 등장하였다.
Optional 소개
다음과 같은 Person 클래스가 있다고 하자.
public class Person {
private Car car;
public Car getCar{
return car;
}
}
만약 어떤 사람이 차를 소유하지 않고 있다면 Person 클래스의 car 변수는 null 이다.
그러나 car 변수의 자료형이 Car 가 아닌 Optional<Car> 라면
public class Person {
private Optional<Car> car;
...
}
차를 소유 하지 않고 있는 사람의 car 변수는 텅빈 Optional 객체를 반환한다.
null 참조는 NullPointerException을 발생시키지만 텅빈 Optional 객체는 그렇지 않다.
우리는 null 이 아닌 Optional 객체(비록 텅비었을 지라도) 여러가지 방법으로 활용할 수 있다.
그 방법을 차근히 알아보자.
Optional 만들기
1. 빈 Optional
empty() 메서드를 이용해 빈 Optional 객체를 얻을 수 있다.
Optional<Car> optCar = Optional.empty();
2. null 이 아닌 값으로 Optional 만들기
of 메서드를 이용하여 만들 수 있다.
Optional<Car> optCar = Optional.of(car);
3. null 일 수 있는 값으로 Optional 만들기
만약 Car 가 null 일 수 있는 상황에서는 ofNullable 메서드를 이용하자
Optional<Car> optCar = Optional.ofNullable(car);
Optional 사용하기
값 추출 및 변환, map
optional 의 사용법은 스트림과 매우 유사하다
optional 은 map 메서드를 지원하는데 만약 Optional 이 값을 포함하고 있다면 map 의 인수로 제공된 함수가 값을 바꾸고, Optional 이 비어있다면 아무일도 하지 않는다.
Optional<String> name = optCar.map(Car::getName);
이 외에도 filter 등 스트림에서 사용하는 연산을 Optional 에서도 사용가능하다.
값 얻기, orElse
그렇다면 Optional 에 저장되어 있는 값은 어떻게 얻을 수 있을가?
orElse() 메서드를 활용할 수 있다.
만약 빈 Optional 일 경우에는 orElse 의 인자로 주어진 값을 리턴한다.
public String getName(Optional<Car> car){
return car.map(Car::getName)
.orElse("Unknown");
}
orElse(T other) 보다는 orElseGet(Supplier<? extends T> other)
orElse 는 유용한 메서드이지만 orElseGet 을 사용하기를 추천한다.
orElse 는 Optional 값의 유무에 상관없이 전달된 함수가 실행되지만 orElseGet 은 Optional 에 값이 비어있는 경우에만 인자로 전달된 함수가 호출된다. 한마디로 orElse 의 게으른 버전이라고 할 수 있다.
public class Car {
private String name;
public Car(final String name) {
System.out.println(name);
this.name = name;
}
}
Car 객체가 생성될 때 이름을 출력한다면 다음과 같은 결과를 얻을 수 있다.
public class Main {
public static void main(String[] args) {
Car car = new Car("car");
Optional<Car> optCar = Optional.ofNullable(car);
optCar.orElse(new Car("orElse"));
optCar.orElseGet(() -> new Car("orElseGet"));
//출력 결과
// car
// orElse
}
}
값이 비었을 경우 예외처리, orElseThrow
만약 값을 얻으려고하고 값이 없는경우 예외를 발생시키려 한다면 orElseThrow 메서드를 사용할 수 있다.
filter 와 함께 사용한다면 다음과 같이 사용할 수 있겠다.
Car car = new Car("k3");
Optional<Car> optCar = Optional.ofNullable(car);
optCar.filter(c -> "k5".equals(c.getName()))
.orElseThrow(() -> new IllegalArgumentException("k5 차량이 아닙니다."));
optCar에는 이름이 k3인 Car 객체가 들어있고, filter 를 거쳐 optCar 는 빈 객체가 된다.
이때 orElseThrow 는 optCar 가 비어 있기 때문에 인자로 전달된 예외를 발생 시킨다.
ifPresent, isPresent
ifPresent(Consumer<? super T> consumer) 는 값이 존재할 경우 인수로 전달된 동작을 실행하고 값이 비어있을 경우 아무일도 일어나지 않는다.
isPresent 메서드는 값이 있는지 없는지 boolean 값을 리턴한다.
'Java' 카테고리의 다른 글
정적팩토리 메서드 명명 규칙 (0) | 2020.05.14 |
---|---|
디폴트 메서드 정리 (0) | 2020.04.24 |
람다 표현식을 활용한 디자인패턴 - 팩토리 패턴 (0) | 2020.04.14 |
람다 표현식을 활용한 디자인패턴 - 의무 체인 패턴 (0) | 2020.04.11 |
람다 표현식을 활용한 디자인패턴 - 옵저버 패턴 (0) | 2020.04.08 |