Optional의 등장
프로그래밍을 하다보면 NullPointException을 종종 보게 된다. null을 리턴하면 안되는데 null을 리턴하는 경우 생기는 예외인데, 이를 처리하는 방법은 크게 2가지가 있다.
- 예외를 던지는 방법
public Something getSomething() {
if(this.something == null) throw new IllegalStateException();
return something;
}
예외를 던질 때 자바는 stack trace에 대한 정보를 생성하고 보여주는데, 이는 리소스를 사용하기 때문에 로직 처리에 예외 처리를 하지 않는 것이 좋다. 따라서 클라이언트 코드가 null을 체크하는 방법을 주로 사용한다.
- 클라이언트 코드가 null을 체크하는 방법
Progress progress = spring_boot.getProgress();
if(progress != null) {
System.out.println(progress.getStudyDuration());
}
위와 같은 코드를 더욱 더 명시적으로 표현하고, null을 잘 처리하기 위해 자바8 에서는 Optional<T>을 제공한다.
Optional의 기본 사용
연습에 사용될 간략화 된 코드는 다음과 같다. OnlineClass는 id, title, closed, progress 멤버변수와 getter, setter를 가지고 있다.
Optional1은 값을 가지고 있고, Optional2는 값을 가지고 있지 않다.
public class OnlineClass {
private Integer id;
private String title;
private boolean closed;
public Progress progress;
}
List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1, "spring boot", true));
springClasses.add(new OnlineClass(5, "rest api development", true));
Optional<OnlineClass> optional = springClasses.stream()
.filter(oc -> oc.getTitle().startsWith("spring"))
.findFirst();
Optional<OnlineClass> optional2 = springClasses.stream()
.filter(oc -> oc.getTitle().startsWith("jpa"))
.findFirst();
isPresent()
isPresent()는 현재 Optional에 값이 들어있는지 확인하고 boolean값으로 리턴하는 함수이다.
boolean flag = optional.isPresent();
System.out.println("spring 포함한 title 있음 = " + flag);
get()
get()은 현재 Optional안에 객체가 있으면 리턴하고, 없으면 RuntimeException을 발생시킨다. get()의 경우 다른 api로 커버할 수 있으므로 사용을 최대한 자제해야 한다.
OnlineClass oc = optional.get();
System.out.println(oc.getId() + " " + oc.getTitle());
OnlineClass oc2 = optional2.get(); // 비어있기 때문에 RuntimeException 발생
ifPresent()
Consumer람다식을 쓸 수 있으며 if문을 쓰지 않고 간편하게 처리 할 수 있다.
if(optional.isPresent()) {
System.out.println(optional.get().getTitle());
}
// 위의 코드를 다음과 같이 간편하게 표현할 수 있다.
optional.ifPresent(p -> p.getTitle());
orElse()
만약 객체가 비어있으면 Else()문 안의 코드를 실행한 뒤 리턴한다.
OnlineClass onlineClass1 = optional.orElse(createNewClass());
System.out.println(onlineClass1.getTitle());
OnlineClass onlineClass2 = optional2.orElse(createNewClass());
System.out.println(onlineClass2.getTitle());
createNewClass()는 "creating new class" 출력하고 새로운 class를 생성하는 코드이다. 그런데 onlineClass1은 값이 있는데 createNewClass()가 실행된 것을 확인할 수 있다.
이럴 때ㅐ 사용할 수 있는 것이 orElseGet이다.
orElseGet()
함수형 인터페이스로 supplier를 사용한다. supplier는 lazy evaluation이 필요할 때 주로 이용되는데 이는 다음 포스팅을 참고하자.
OnlineClass onlineClass3 = optional.orElseGet(OptionalMain::createNewClass);
System.out.println(onlineClass3.getTitle());
orElseThrow()
Optional에 값이 있으면 가져오고 없으면 에러를 발생시킨다.
OnlineClass onlineClass4 = optional2.orElseThrow();
filter(Predicate)
Optional에서도 filter를 사용할 수 있다.
Optional<OnlineClass> onlineClass = optional.filter(OnlineClass::isClosed);
System.out.println(onlineClass.isPresent());
map(Function)
Optional<String> title = optional.map(OnlineClass::getTitle);
System.out.println(title.get());
flatMap
map의 리턴 값으로 Optional<>이 리턴 된다면 다음과 같이 두 번 Optional을 벗겨내는 일을 해야 한다.
Optional<Optional<Progress>> progress = optional.map(OnlineClass::getProgress);
Optional<Progress> progress1 = progress.orElseThrow();
Progress progress2 = progress1.orElseThrow();
flatMap은 이런 과정을 줄여준다.
Optional<Progress> progress3 = optional.flatMap(OnlineClass::getProgress);