자바 8부터 지원하는 Optional에 대해서 알아보자.
Optional 이란?
java.util.Optional<T> 클래스
Optional<T>클래스는 Integer나 Doble 클래스처럼 'T' 타입의 객체를 포장해주는 래퍼 클래스(Wrapper class)이다. 따라서 Optional 인스턴스는 모든 타입의 참조 변수를 저장할 수 있다.
그럼 Optional은 어떨 때 사용하는가?
Optional 객체를 사용하면 예상치 못한 NPE(NullPointerException) 예외를 제공되는 메소드로 간단히 회피할 수 있다. 즉, 복잡한 조건문없이 null값으로 인한 예외를 처리할 수 있게 된다.
Optional 객체의 생성
of()메소드나 ofNullable() 메소드를 사용해서 Optional 객체를 생성할 수 있다.
- of() : null이 아닌 명시된 값을 가지는 Optional객체를 생성한다. of()를 통해 null값을 저장하려 하면, NPE 예외가 발생한다.
- ofNullable() : 명시된 값이 null이 아니면 명시된 값을 가지는 Optional 객체를 반환하며, 명시된 값이 null이면 비어있는 Optional 객체를 반환한다.
// coffee의 이름을 가져와서 Optional로 감싼 다음 값이 있다면 setter 메서드로 값을 세팅
Optional.ofNullable(coffee.getKorName())
.ifPresent(findCoffee::setKorName);
// coffee의 이름이 없다면 No Name으로 세팅
Optional.ofNullable(coffee.getKorName())
.orElse("No name");
Optional<String> optional1 = Optional.ofNullable("Optional1");
Optional<String> optional2 = Optional.of("Optional2");
Optional 사용법
get()메소드를 사용하면 Optional 객체에 저장된 값에 접근할 수 있다. 만약 저장된 값이 null이라면 NoSuchElementException 예외가 발생한다. 따라서 get()메소드를 호출하기 전 isPresent()메소드를 사용해서 null인지 아닌지 체크해줘야 한다.
Optional<String> optional = Optional.ofNullable("Optional Test");
// null 인지 체크
if(opt.isPresent()) {
System.out.println(optional.get());
}
String name = "hello";
System.out.println(Optional.ofNullable(name).orElse(()->"NULL"));
name = null;
System.out.println(Optional.ofNullable(name).orElseGet(()->"NULL"));
//출력값
Optional Test
hello
NULL
null인지 체크하는 isPresent()메소드 말고 위처럼 다른 메소드를 활용할 수도 있다.
- orElse() : 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 값을 반환한다. 해당 값이 null이든 아니든 관계없이 항상 불린다.
- orElseGet() : 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 람다 표현식의 결괏값을 반환한다. 해당 값이 null일때만 불린다.
- orElseThrow() : 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 예외를 발생시킨다.
Optional 객체를 null로 초기화 할 수도 있다.
Optional<String> optional = Optional.empty();
System.out.println(optional.orElse("null입니다."));
System.out.println(optional.orElseGet(String::new));
// 출력값
null입니다.
orElse와 orElseGet의 차이
사용법에서 적어놓았던 것처럼 이 둘의 차이점은 실행여부이다.
- orElse: null이 아니어도 실행
- orElseGet: null일 때만 실행
이 차이점은 보기엔 단순해보여도 아주 중요하다. 잘못 사용했다가 장애가 발생할 수 있기 때문이다.
public User findByUsername(String name) {
return userRepository.findByName(name).orElse(createUserWithName(name));
}
private User createUserWithName(String name) {
User newUser = new User(name);
return userRepository.save(user);
}
orElse는 null이 아니어도 해당 메서드를 실행하기 때문에 name에 값이 있어도 createUserWithName() 메서드를 실행할 것이다. 만약 DB에 name값이 unique로 설정돼있다면 오류가 발생할 것이다.
그렇기 때문에 이러한 상황에서는 orElseGet()메서드를 사용해야한다.
public User findByUsername(String name) {
return userRepository.findByName(name).orElseGet(this::createUserWithName(name));
}
private User createUserWithName(String name) {
User newUser = new User(name);
return userRepository.save(user);
}
이 차이점을 고려하여 적절한 메서드를 골라야할 것이다.
정리
Optional을 무분별하게 사용하는 것은 좋지 않다. null을 반환하여 오류가 발생할 가능성이 높을 경우에 사용하는 것이 적합하다. NPE대신 NoSuchElementException 예외가 발생할 수 있으며, 이전에 없었던 문제들이 발생할 수도 있고, 코드의 가독성을 떨어트린다. 공간비용과 시간비용도 증가하니 반드시 사용할 때를 구분지어 사용하도록 하자.
Reference
https://mangkyu.tistory.com/70
http://www.tcpschool.com/java/java_stream_optional