@Test
void 초기화() {
Optional<String> opt = Optional.of("Hello");
Optional<String> opt3 = Optional.empty();
}
Optional 객체를 초기화하는 가장 기본적인 방법은 2가지가 있다. 하나는 비어있는 Optional을 만드는 것(.empty()
)이고, 또 하나는 특정 값을 감싸서 Optional을 만들어주는 방법(.of()
)이다.
주의할 점은 Optional.of(null)
과 같이 .of()
에 null
값이 들어가면, NPE가 발생한다는 점이다. 대부분의 경우 Optional을 쓰는 이유는 어떠한 변수에 확정적으로 값이 할당되는 것이 보장되지 않고, null
이 들어갈 수도 있어서 이 변수를 처리하는 로직에서 NPE가 발생하지 않도록 예방하는 것인데, null
이 들어갔을 때 에러가 나도록 되어 있다면,,, 무슨 의미인가 싶다 ?!?
그래~서 이러한 경우도 처리할 수 있는 방법 있는데 바로 이어서 확인해보자.
@Test
void 초기화() {
Assertions.assertThatThrownBy(() -> Optional.of(null))
.isInstanceOf(NullPointerException.class);
Optional<String> opt = Optional.ofNullable(null);
}
이어서 이야기하자면, null
이 될 수도 있는 값을 Optional로 만들고 싶다면 Optional.ofNullable()
을 사용하면 된다. 🤓
Optional에 값이 있는지 여부에 따라서 이후 처리들이 달라지는 경우가 많을 텐데, 이번에는 Optional이 비어있는지 아닌지 체크하는 메서드들에 대해서 알아보자.
@Test
void 값의_존재_여부_체크() throws Exception {
assertThat(Optional.of("Hello").isPresent()).isTrue();
assertThat(Optional.empty().isEmpty()).isTrue();
}
메서드 명에서도 알 수 있듯이 .isPresent()
는 Optional에 값이 있으면 true
를 리턴하는 메서드이고, isEmpty()
는 Optional이 비어 있으면 true
를 리턴하는 메서드이다.
@Test
void 값의_존재_여부_체크() throws Exception {
Optional.of("Hello").ifPresent(s -> {
log.info("{} 있다", s);
// 무언가 수행
});
}
값의 존재 여부를 체크하면서 값이 존재할 경우 특정 로직을 수행할 수 있는 ifPresent()
도 있다.
/**
* If a value is present, performs the given action with the value,
* otherwise does nothing.
*
* @param action the action to be performed, if a value is present
* @throws NullPointerException if value is present and the given action is
* {@code null}
*/
public void ifPresent(Consumer<? super T> action) {
if (value != null) {
action.accept(value);
}
}
여기서 수행할 로직은 ifPresent()
에 Consumer로 넘겨주면 된다.
Optional이 비어 있는 경우, 단순히 비어 있는 것을 확인하는 것만 아니라 다양한 처리를 추가할 수 있는데, 그 방법들을 알아보자.
@Test
void orElse() throws Exception {
Optional<Object> emptyOptional = Optional.empty();
assertThat(emptyOptional.orElse("abc")).isEqualTo("abc");
assertThat(emptyOptional.orElseGet(() -> "abc")).isEqualTo("abc");
}
값이 비어있을 때, 대체할 값을 리턴하고 싶다면, .orElse()
를 사용하고, 특정 메서드를 통해서 대체할 값을 리턴하고 싶다면, .orElseGet()
을 사용할 수 있다.
이 둘의 역할은 동일하고, 상황에 맞게 적합한 것을 사용하면 된다.
@Test
void orElse() throws Exception {
Optional<Object> emptyOptional = Optional.empty();
assertThatThrownBy(() -> emptyOptional.orElseThrow(() -> new IllegalArgumentException("비어 있음")))
.isInstanceOf(IllegalArgumentException.class);
}
값이 비어 있을 때, 예외를 던지고 싶다면 .orElseThrow()
를 사용하자.
@Test
void 값_가져오기() throws Exception {
assertThat(Optional.of("abc").get()).isEqualTo("abc");
assertThatThrownBy(() -> Optional.empty().get())
.isInstanceOf(NoSuchElementException.class);
}
Optional은 어쨋든 NPE를 예방하기 위한 객체이고, 실제적인 로직을 처리하기 위해서는 Optional에 담겨 있는 실제 값을 가져와야하는 경우가 많다.
이 때 사용할 수 있는 가장 기본적인 메서드가 .get()
이다. 단, 비어있는 Optional에 .get()
을 사용하면 NoSuchElementException
예외가 발생한다.
@Test
void 값_가져오기_filter() throws Exception {
Optional<String> opt = Optional.of("abc");
assertThat(opt.filter(s -> "abc".equals(s)).get()).isEqualTo("abc");
assertThatThrownBy(() -> opt.filter(s -> "hello".equals(s)).get())
.isInstanceOf(NoSuchElementException.class);
}
특정 조건을 만족하는 값만을 걸러낼 수 있는데, 이 때 사용할 수 있는 메서드가 .filter()
이다.
@Test
void 값_가져오기_map() throws Exception {
Optional<String> opt = Optional.of("abc");
assertThat(opt.map(String::length).get()).isEqualTo(3);
}
List의 .map()
메서드처럼 값을 변형해서 가져올 수 있는 메서드가 .map()
이다.
public void someMethod(Optional<String> badArgument) { // ❌
// ...
}
사용하면 에러가 나는 것은 아니지만, 메서드의 Input은 명확할 수록 좋기 때문에 Optional을 메서드의 매개변수로 사용하는 것은 지양...도 아니고, 하지 말자!
public class SomeClass {
private Optional<String> badField; // ❌
// ...
}
이 또한 이렇게 사용하다고 해서 에러가 나는 것은 아니지만, 멤버변수는 충분히 명확한 값으로 지정할 수 있으므로, 이렇게 사용하는 것은 지양...이 아니라 하니 말자!
💡 Optional은 메서드의 리턴값 또는 그 값을 받아서 처리하는 곳에서만 사용하자!
원시 타입의 값을 Optional로 감싸야할 경우, Optional
대신 아래의 클래스들을 사용할 것을 권장한다.
Optional을 사용하면서 발생할 Boxing, Unboxing 처리를 간소화해서 불필요한 리소스 낭비를 방지할 수 있다.
OptionalInt
OptionalLong
OptionalDouble
Java는 기본적으로 강타입 언어이고, 최대한 명확하게 사용되는 타입들을 명시해주는 것이 좋다. 그러니 Optional을 만능 도구처럼 사용하지 말고, 꼭 필요한 경우에만 적절히 사용하도록 하자.
예를 들어, DB에서 값을 조회해서 가져오는 경우, 그 결과가 어떻게 될지 확신할 수 없으므로 Optional을 사용하는 것이 적합하지만 그 이후에 처리에서 Optional을 남용하는 것은 좋지 않을 수 있다.