💻IT/Java

[Java] Java 8 Optional

gom20 2021. 12. 9. 20:24

참고 

https://www.oracle.com/technical-resources/articles/java/java8-optional.html

https://www.baeldung.com/java-difference-map-and-flatmap

NullPointerException !?

개발을 하면서 많이 경험했던 Exception은 아마 NullPointerException 이 아닐까 싶다. 

위와 같은 모델 구조가 있다고 가정했을 때,

차에 포함된 네비게이션의 버전을 알고 싶다. 

String version = computer.getNavigation().getVersion();

Car에서 Navigation은 옵션 중 하나이다.  

만약 Navigation이 없는 차라면 해당 코드 호출 시 NullPointerException 이 발생하게 된다. (null.getVersion())

그러면 어떻게 해야 에러가 발생하는 것을 막을 수 있을까? 

아래와 같이 방어 코드를 구현할 수 있다. 하지만 nested 체크로 인해 가독성이 떨어지고 

Null 체크를 위해 수많은 보일러 플레이트 코드를 구현해야 한다. 

String version = "UNKNOWN";
if(car != null){
  Navigation navigation = car.getNavigation();
  if(navigation != null){
     version = navigation.getVersion();
  }
}

 

JAVA 8 Optional

Java8에서 이러한 Null Exception 문제를 효과적으로 다룰 수 있는시켜 줄 java.util.Optional 클래스를 도입하였다.

Optional은 간단히 말해서 옵셔널한 Value를 포함하는 클래스이다. 

Value가 포함되어 있을 수도 있고, 없을 수도 있다. 

 

Navigation 버전을 알고 싶을 때, Optional의 메소드를 통해 아래와 같이 구현할 수 있다. 

public class Car {
  private Optional<Navigation> navigation;  
  public Optional<Navigation> getNavigation() { ... }
  ...
}

public class Navigation{
  public String getVersion(){ ... }
}
String name = car.flatMap(Car::getNavigation)
                          .map(Navigation::getVersion)
                          .orElse("UNKNOWN");

 

Optional 객체 생성

empty 객체 생성

Optional<Soundcard> sc = Optional.empty();

non-null value 객체 생성

Navigation navigation = new Navigation();
Optional<Navigation> navi = Optional.of(navigation);

 

 

Optional.of로 null 이 전달될 경우 nullpointerException이 발생한다. 

Navigation navigation = null;
Optional<Navigation> navi = Optional.of(navigation);

 

Value 객체가 Null 일 가능성이 있다면 ofNullable을 사용한다.

Optional<Navigation> navi = Optional.ofNullable(navigation);

 

Value가 존재한다면?

ifPresent()

navigation 이 존재한다면 출력

AS-IS
if(navigation != null){
    System.out.print(navigation);
}

TO-BE
Optional<Navigation> navi = Optional.ofNullable(navigation);
navi.ifPresent(System.out::print);

 

isPresent()로 Value가 존재하는지 여부를 체크할 수 있다. 

get() 은 Optional에 포함된 value를 리턴한다.

if(navigation.isPresent()){
    System.out.print(navigation.get());
}

 

Optional로 Default Value처리

Navigation navigation = (maybeNavi != null)? maybeNavi : new Navigation("default");

TO-BE

Optional클래스의 orElse로 Value가 Null 일 경우 Default 값 리턴

Optional<Navigation> maybeNavi = Optional.ofNullable(navigation);
Navigation navigation = maybeNavi.orElse(new Navigation("default"));

orElseThrows()로 Null일 경우 Exception 을 던지는 것도 지원

 

Filter Method 사용 시 특정 Value Rejecting

AS-IS
Navigation navi = ...;
if(navi != null && "3.0".equals(navi.getVersion())){
  System.out.println("ok");
}

TO-BE
Optional<Navigation> maybeNavi = ...;
maybeNavi.filter(navi -> "3.0".equals(navi.getVersion())
                    .ifPresent(() -> System.out.println("ok"));

 

Map Method로 Value 추출하기 

Optional<Navigation> maybeNavigation = maybeCar.map(Navigation::getNavigation);
maybeCar.map(Navigation::getNavigation)
      .filter(navi -> "3.0".equals(navi.getVersion())
      .ifPresent(() -> System.out.println("ok"));