brunch

You can make anything
by writing

C.S.Lewis

by 강관우 Apr 05. 2020

Java Optional Guide

Java.util.Optional class

안녕하세요. 이 글은 java.util 패키지의 Optional 에 대하여 다루려고 합니다.


Optional class는 값이 존재하는지 여부를 명시적으로 체크하기 위해 등장했습니다. 이 글을 통해서 Optional의 이론적인 측면과 Optional 인스턴스나 메소드를 생성하는 방법을 다루겠습니다.


Java Optional 소개

Optional은 새로운 컨셉은 아니고 Haskell이나 Scala 언어에서 이미 구현된 컨셉입니다.

메소드 실행 시 반환하는 값을 알 수 없을 때 혹은 그 값이 존재하지 않을 때(null)를 모델링하는 경우, 이 컨셉이 매우 유용하게 사용될 수 있습니다. 자바에서는 이 컨셉을 특정 값(null을 포함할 수 있는)을 담는 container 객체인 Optional로 표현했습니다.


기술적인 측면에서 보자면 Optional은 value based class라고 볼 수 있습니다. 이 말이 뜻하는 건 다음과 같습니다.


- Optional 인스턴스는 불변 인스턴스이다.

- hashCode, equals, toString 메서드는 인스턴스 상태(value)에 따라 달라집니다.

- Optional 인스턴스는 value의 equals 메서드에 의해서 동일성이 판단된다.


Optional Instance 생성

코드 예제들은 AssertJ 라이브러리를 사용했습니다. assertJ 학습 링크를 공유합니다.


Optional 객체의 인스턴스를 생성하는 방법은 여러 가지가 있습니다. 

언급한 듯이 Optional은 불변 객체이므로 모든 방법들은 생성자 대신 factory methods를 사용합니다.


Using of()

가장 흔하게 사용되는 방법은 of() factory method를 사용하는 방법입니다. 이 메서드는 인자로 주어진 값을 가지는 optional 인스턴스를 반환합니다. 여기에 Null을 넣을 수는 없습니다.  Null이 들어간다면 NullPointerException이 발생합니다. 



1| Optional<Interger> result1 = Optional.of(10);

2| assertThat(result1).isInstanceOf(Optional.class).isPresent().contains(10);



From streams

몇몇 Stream API의 종료 메서드는 Optional 인스턴스를 결과로 반환합니다. 이를 통해 결과값을 다루거나 존재 여부를 확인할 수 있습니다. 


 findAny, findFirst, max, min, reduce


1| List<Interger> numbers = Lists.newArrayList(1,2,3,4,5);

2| Optional<Integer> result2 = numbers.stream().filter(n -> n>2).findFirst();

3| assertThat(result2).isInstanceOf(Optional.class).isPresent().contains(3);



From nullable

첫 번째로 소개한 factory metohd인 of()는 null이 아닌 값만 다룰 수 있다는 단점이 있습니다. 

만약 값이 Null일 수 있다면 ofNullable 메서드를 사용하는 것을 권장합니다. 이 메서드는 주어진 value를 담는 Optional 인스턴스를 생성하는데 null일 경우 empty optional을 반환합니다.



1| String name = null;

2| Optional<String> result3 = Optional.ofNullable(name);

3| assertThat(result3).isInstanceOf(Optional.class).isEmpty();



Create empty optional 

마지막으로 명시적으로 empty optional을 생성하는 방법에 대해 소개합니다.


1| Optional<String> result4 = Optional.empty();

2| assertThat(result4).isInstanceOf(Optional.class).isEmpty();



Optional을 활용하는 방법


Optional을 사용하면 Null checking을 로직 속에 넣을 수 있다는 장점이 있습니다. 이를 통해 NPE 발생 확률을 줄일 수 있죠. 명시적인 Null Checking을 하면서 값을 사용하는 방법들을 소개합니다. 


IfPresent()

value가 존재할 때만 수행되는 로직이 있을 때 ifPresent() 메서드를 사용할 수 있습니다. 이 메서드는 인자로 값이 존재할 때만 실행하는 콜백함수를 넣어줄 수 있습니다.



1| String name = "Gwanwoo";

2| Optional<String> result = Optional.of(name);

3| result.ifPresent(n -> System.out.println(n));



IfPresentOrElse()

IfPresent() 메서드는 값이 존재할 때만 실행할 로직을 넣어줄 수 있었습니다. 값이 존재하지 않을 때에 수행할 로직도 넣고 싶다면 IfPresnetOrElse() 메서드를 사용하면 됩니다. ( 이 메서드는 JDK 9에 추가됐습니다.)


이 함수는 두 개의 인자를 받습니다.

- 값이 존재할 때 실행될 콜백 함수

- 값이 존재하지 않을 때 실행될 Runnable 인터페이스



1| Optional<Integer> result = Optional.empty();

2| result.IfPresentOrElse(n -> System.out.println("Result is: " + n),

3|                                         () -> System.out.println("No result found"));



get() and orElse()

Optional 객체는 value를 담고 있는 컨테이너 객체입니다. Optional에 담긴 value를 얻기 위한 unpack 메서드로 get() 와 orElse()를 알아보겠습니다. 


get() 메서드는 value를 반환하고 value가 없다면 NullPointerException을 발생시킵니다.

orElse() 메서드는 value를 반환하고 value가 없다면 인자로 받은 값을 반환합니다. 



1| Optional<Interger> result = Optional.empty();

2| Interger value = result.orElse(10);

3| assertThat(value).isEqualTo(10);



orElseThrow()

이 메서드는 JDK 11에서 소개된 메서드입니다. 값이 있다면 반환하고, 없으면 NoSuchElementExcepion을 던집니다.



1| Optional<String> result = Optional.empty();

2| assertThatExceptionThrownBy(() -> 3|result.orElseThrow()).isInstanceOf(NoSuchElementException.class);



map()

Optional 은 매핑을 위한 함수인 map() 메서드를 제공합니다. 이 메서드는 값이 존재하면 실행될 mapper function을 인자로 받습니다. map 메서드는 mapper 함수를 실행하고 만든 value 값을 담은 optional을 반환합니다. 



1| Optional<String> name = Optional.of("gwanwoo");

2| Optional<String> result = name.map(n -> n.toUpperCase());

3| assertThat(result).isPresent().contains("GWANWOO");



Create Stream From Optional


Stream API 메서드 중에는 Optional 객체를 만드는 메서드가 있었는데요, Optional 에도 Stream을 만드는 API가 있습니다. 



1| Optional<Interger> number = Optional.of(10);

2| long result = number.stream().count()

3| assertThat(result).isEqualTo(1);



브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari