Logo

Java9에서 추가된 Optional 기능 소개

Java9에서 Optoinal 클래스에 몇가지 유용한 메서드들이 추가되었습니다. 이번 포스팅에서는 이 새로운 메서드들을 어떻게 활용할 수 있는지 살펴보겠습니다. Java8에서 추가되었던 Optional의 좀 더 기본적인 사용법은 아래 포스팅를 참고바랍니다.

or() 메서드

Optional 객체가 담고 있는 값에 접근할 때, 비어있는 경우를 대비해서 orElse​()orElseGet() 메서드를 이용해서 대신 리턴할 값을 지정해줬었습니다. orElse() 메서드는 대신 리턴할 값 자체를 인자로 받는 반면, orElseGet() 메서드는 대신 리턴할 값을 만들어내는 람다를 인자로 받습니다. Java9에서 추가된 or() 메서드를 얄아보기 전에 먼저 이름이 유사한 기존 메서드을 먼저 리마인드 해보겠습니다.

orElse()

예를 들어 커피 주문 시스템에서 주문한 커피 이름이 누락된 경우 디폴트로 아메리카노를 사용한다고 해봅시다. 아래는 orElse() 메서드를 이용해서 주문한 커피 이름이 null인 경우, coffeeToMake 변수를 아메리카노(Americano)로 세팅해주는 코드입니다.

String coffeeToMake = Optional
  .ofNullable(order)
  .map(Order::getCoffee)
  .orElse("Americano");
System.out.println("만들 커피: " + coffeeToMake);

만약, order.getCoffee()"Latte"를 리턴한다면, orElse() 메서드 라인은 실행될 일이 없게 됩니다.

만들 커피: Latte

하지만 order.getCoffee()null을 리턴하거나, order 객체 자체가 null인 경우에는 orElse() 메서드의 인자값으로 넘어간 "Americano"coffeeToMake 변수에 할당됩니다.

만들 커피: Americano

orElseGet()

고객 맞춤 서비스를 제공하기 위해서 커피 주문 시스템을 개선 해보겠습니다. 디폴트 커피 이름을 아메리카노처럼 고정된 값이 아닌 해당 고객이 즐겨마시는 커피 이름으로 세팅하려면 어떻게 해야할까요?

아래는 orElseGet() 메서드를 이용해서 주문한 커피 이름이 null인 경우, coffeeToMake 변수를 고객이 설정해 둔 즐겨마시는 커피 이름으로 세팅해주는 코드입니다.

String coffeeToMake = Optional
  .ofNullable(order)
  .map(Order::getCoffee)
  .orElseGet(() -> user.getFavoriteCoffee());
System.out.println("만들 커피: " + coffeeToMake);

마찬가지로 order.getCoffee()"Latte"를 리턴한다면, orElseGet() 메서드 라인은 실행될 일이 없게 됩니다.

만들 커피: Latte

하지만 order.getCoffee()null을 리턴하거나, order 객체 자체가 null인 경우에는 orElseGet() 메서드의 인자로 넘어간 람다 함수의 리턴값이 coffeeToMake 변수에 할당됩니다. 만약에 user.getFavoriteCoffee()의 리턴값이 "Cappuccino"라면 다음과 같이 출력될 것입니다.

만들 커피: Cappuccino

orElseGet() 메서드는 orElse() 메서드와 달리 런타임에 고정되지 않는 값을 지연 로딩(Lazy Loading)할 수 있습니다. 그리고 이 두 개의 메서드 모두 Optional 객체 자체(Optional<T>)가 아닌 Optional 객체가 담고 있던 값(T)을 리턴한다는 공통점이 있습니다. 위 코드에서 coffeeToMake 변수의 타입이 Optional<String>이 아닌 String인 이유입니다.

or()

Java9에서 추가된 or() 메서드는 위에서 복습한 orElseGet()orElse()와 달리 Optional 객체를 리턴하고 싶을 때 사용합니다. 따라서 인자로 넘기는 람다 함수는 T가 아닌 Optional<T>를 리턴해야 합니다.

예를 들어 고객이 즐겨마시는 커피 이름을 설정하지 않았을 경우, 위에 orElseGet() 메서드를 사용하는 코드는 결국 coffeeToMake 변수에 null을 할당하게 될 것입니다. 왜냐하면, orElseGet() 메서드의 인자로 넘어간 람다 함수가 null을 리턴할 것이기 때문입니다.

만들 커피: null

자 그럼, or() 메서드를 활용해서 커피 주문 시스템을 한 단계 더 업그레이드 해보겠습니다. 이번에는 고객이 즐겨 마시는 커피가 있을 경우, 그 커피 이름로 사용하고, 없을 경우에는 아메리카노를 사용하도록 코드를 바꿔보겠습니다.

String coffeeToMake = Optional
  .ofNullable(order)
  .map(Order::getCoffee)
  .or(() -> Optional.ofNullable(user.getFavoriteCoffee()))
  .orElse("Americano");
System.out.println("만들 커피: " + coffeeToMake);

이렇게 코드를 변경하게 되면 user.getFavoriteCoffee()의 리턴값이 null이라도 다음과 같이 출력되게 될 것입니다.

만들 커피: Americano

or() 메서드의 인자로 넘어간 람다 함수는 Optional.ofNullable() 메서드를 사용하여 고객이 즐겨 마시는 커피 값을 Optional 객체에 담아서 리턴하고 있습니다. or() 메서드의 리턴 타입이 Optonal이기 때문에 계속해서 orElse()와 같은 Optional 클래스의 메서드들을 연쇄적으로 호출할 수 있게됩니다.

기존의 orElseGet()orElse()가 주로 메서드 채인(Chain)의 최말단에서 최종적으로 Optional 객체가 담고 있는 값을 얻기위해서 사용됐다고 한다면, 이번에 추가된 or() 메서드는 주로 메서드 채인의 중간에서 Optionl 객체가 비어있을 경우 값을 채워주기 위한 용도로 사용할 수 있습니다.

이렇게 주문한 커피 이름이나 고객이 즐겨마시는 커피 이름이 누락되어도 항상 디폴트로 아메리카노를 만들 수 있는 주문 시스템을 만들어졌습니다.

ifPresentOrElse() 메서드

Java9에서 추가된 ifPresentOrElse() 메서드는 기존에 있던 ifPresent() 메서드와 이름이 유사합니다. 차이점이라고 하면 기존의 ifPresent() 메서드는 Optional 객체가 값을 담고 있을 때 처리할 내용만 정의했다고 한다면, ifPresentOrElse() 메서드는 그와 더불어, Optional 객체가 비어있을 경우 처리할 내용까지 정의할 수 있습니다.

ifPresent()

아래는 ifPresent() 메서드를 사용하여 주문한 커피 이름이 있을 경우에만 만들 커피를 출력하는 코드입니다.

Optional.ofNullable(order)
  .map(Order::getCoffee)
  .ifPresent(coffee -> System.out.println("만들 커피: " + coffee));

인자로 Optional 객체가 담고 있는 값을 가지고 수행할 로직을 넘기기 때문에, order.getCoffee()null을 리턴하거나, order 객체 자체가 null인 경우에는 Optional 객체가 비게 되어 아무 것도 출력되지 않습니다.

ifPresentOrElse()

주문한 커피 이름이 없을 경우 고객이 즐겨 마시는 커피 이름울 출력할 수 있도록 ifPresentOrElse() 메서드를 사용하여 코드를 수정하였습니다.

Optional.ofNullable(order)
  .map(Order::getCoffee)
  .ifPresentOrElse(
    coffee -> System.out.println("만들 커피: " + coffee),
    () -> System.out.println("만들 커피: " + user.getFavoriteCoffee())
  );

ifPresentOrElse() 메서드는 두 개의 인자를 받는데, 첫 번째 인자는 ifPresent() 메서드와 동일하며, 두 번째 인자는 Optional 객체가 비어있을 경우, 수행될 람다 함수입니다.

stream() 메서드

마지막으로 Java9에서 Optional 클래스에 추가된 메서드는 stream() 입니다. 이 메서드는 Optional 객체를 Stream 객체로 변환하기 위해서 사용됩니다. 기본적으로 Stream 클래스는 Optional 클래스보다 더 다양하고 강력한 API를 가지고 있으며, Optional도 어떻게 보면 값이 하나 이거나 하나도 없는 Stream으로 확장해서 생각해볼 수 있습니다.

예를 들어, 아래는 여러 개의 커피 이름이 각각 Optonal 객체에 담겨서 Stream 형태로 들어오는데, 이중 비어있는 Optonal 객체는 제외하고 존재하는 커피 이름들만 List 객체로 변환하는 코드입니다.

Stream<Optional<String>> optionalStream =
  Stream.of(Optional.of("Americano"), Optional.of("Latte"), Optional.empty(), Optional.of("Cappuccino"));
List<String> validCoffeeList = optionalStream
  .flatMap(Optional::stream)
  .collect(Collectors.toList());
System.out.println("만들 커피 목록: " + validCoffeeList);
만들 커피 목록: [Americano, Latte, Cappuccino]

위와 같이 Optionalstream() 메서드는 단독으로 쓰기 보다는 StreamflatMap 메서드와 함께 쓸 때 빛을 발휘하는 것 같습니다.

마치면서

이상으로 Java9에서 Optional 클래스에 추가된 3개의 메서드 or()ifPresentOrElse(), stream()을 어떻게 사용하는지에 대해서 알아보았습니다. 개인적으로는 or() 메서드의 활용성이 가장 좋아보이는데 다른 메서드들도 익숙해질 수 있도록 더 많이 사용해봐야 겠습니다.

참고