반응형

 

 

 

📌 자바의 옵셔널(Java Optional) 이란?

 

자바 8 이전 버전에서 객체가 null값을 반환할 경우 예외처리 과정을 통해 NullPointException을 방지했었다.

 

자바 8 버전부터는 java.util.Optional<T> 이라는 새로운 래퍼 클래스(Wrapper class)가 추가되었다. 

옵셔널을 사용하면 이전 버전의 복잡한 과정을 간편하게 처리할 수 있기 때문에 유용하게 사용할 수 있다. 

 

옵셔널 사용 시 주의할 점으로 옵셔널은 값을 랩핑(Wrapping)과 언랩핑(Unwrapping) 과정을 거친 후 값이 null일 경우 대체하는 함수를 호출하는 등의 오버헤드가 있어 성능이 저하될 수 있다는 점이다. 따라서 메서드의 반환 값이 null이 나올 수 없을 때는 옵셔널을 사용하지 않는 것이 성능에 도움이 될 수 있다.

 

즉, 옵셔널은 메서드의 결과가 null이 될 수 있는 경우에만 사용하는 것이 좋다.

 

 

 

1️⃣ Optional 객체 생성

Optional 객체를 생성하기 위해서는 아래와 같은 메서드를 사용한다

 

1.1. Optional.of

value가 null 인 경우 NPE 예외를 던진다. 따라서 반드시 값이 있어야 하는 경우에 사용한다.

public static <T> Optional<T> of(T value);

// Example
Optional<String> opt = Optional.of("result");

 

1.2. Optional.ofNullable

value가 null 인 경우 비어있는 Optional을 반환한다. 따라서 값이 null 일수도 있는 경우에 사용한다.

public static <T> Optional<T> ofNullable(T value);

// Example
Optional<String> opt = Optional.ofNullable(null);

 

1.3. Optional.empty

비어있는 옵셔널 객체를 생성한다. 조건에 따른 분기점이 필요하고, 반환할 값이 없는 경우에도 사용할 수 있다.

public static<T> Optional<T> empty();

// Example
Optional<String> emptyOpt = Optional.empty();

 

🙋‍♂️ 비어있는 옵셔널 객체란?

옵셔널 객체가 있지만 옵셔널 객체가 가진 유효한 객체가 없는 경우 비어있다고 표현한다.

* 비어있는 옵셔널 객체
----------
|Optional| 이 옵셔널 객체는 생성은 되었으나 값(객체)을 가지지 않았다.
|        | 이 상태는 Optional.empty() 라고 볼 수 있다.
|        |
----------

* 값이 있는 옵셔널 객체
----------
|Optional| 이 옵셔널 객체는 생성 되고 값(객체)을 가지고 있다.
|--------|
||String||
|--------|
----------

 

 

 

2️⃣ Optional 중간 처리

옵셔널 객체를 생성한 후 사용 가능한 메서드이다.

해당 메서드들은 다시 옵셔널을 반환하므로 메서드 체이닝을 통해 원하는 로직을 반복하여 삽입할 수 있다.

 

2.1. filter

predicate 값이 참이면 해당 필터를 통과시키고 거짓이면 통과되지 않는다.

public Optional<T> filter(Predicate<? super T> predicate);

// Example
Optional.of("True").filter((val) -> "True".eqauls(val)).orElse("NO DATA"); // "True"
Optional.of("False").filter((val) -> "True".eqauls(val)).orElse("NO DATA"); // "NO DATA"

 

2.2. map

mapper 함수를 통해 입력값을 다른 값으로 변환한다.

public<U> Optional<U> map(Function<? super T, ? extends U> mapper);

// Example
Integer test = Optional.of("1").map(Integer::valueOf).orElseThrow(NoSuchElementException::new); // string to integer

 

2.3. flatMap

mapper 함수를 통해 입력값을 다른 값으로 변환한다. map() 과의 차이점은 메서드 시그니처의 매개변수이다. 

map()에서는 제너릭으로 U를 정의했지만 flatMap()에서는 제너릭으로 Optional(U)를 정의했다.

따라서 flatMap() 메서드의 반환 값은 Optional이다.

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper);

// Example
String result = Optional.of("result")
        .flatMap((val) -> Optional.of("good"))
        .get();
System.out.println(result); // print 'good'

 

 

 

3️⃣ Optional 종단 처리

종단 처리는 옵셔널 객체의 체이닝을 끝내는 것이다.

 

3.1. ifPresent

최종 연산을 끝낸 후 값이 비어있지 않다면 입력값이 주어진다.

중간 연산 과정 중 비어있는 옵셔널 객체를 받게 되면 ifPresent() 메서드의 내용을 수행하지 않는다.

public void ifPresent(Consumer<? super T> consumer);

// Example_1
Optional.of("test").ifPresent((value) -> {
	// something to do
});

// Example_2 (ifPresent 미수행)
Optional.ofNullable(null).ifPresent((value) -> {
	// nothing to do
});

 

3.2. isPresent

최종 연산을 끝낸 후 객체의 존재 여부를 판별한다.

public boolean isPresent();

// Example
Optional.ofNullable("test").isPresent(); // true
Optional.ofNullable("test").filter((val) -> "result".eqauls(val)).isPresent(); // false

 

3.3. get

최종 연산을 끝낸 후 객체를 반환한다. 만약, 비어있는 옵셔널 객체라면 예외가 발생한다.

public T get();

// Example
Optional.of("test").get(); // 'test'
Optional.ofNullable(null).get(); // NoSuchElementException!

 

3.4. orElse

최종 연산을 끝낸 후 옵셔널 객체가 비어있다면 기본값으로 제공할 객체를 지정한다.

public T orElse(T other);

// Example
String result = Optional.ofNullable(null).orElse("default");
System.out.println(result); // print 'default'

 

3.5. orElseGet

최종 연산을 끝낸 후 옵셔널 객체가 비어있다면 기본값으로 제공할 공급자 함수 Supplier를 지정한다.

public T orElseGet(Supplier<? extends T> other);

// Example
String result = Optional.ofNullable("input").filter("test"::equals).orElseGet(() -> "default");
System.out.println(result); // print 'default'

 

3.6. orElseThrow

최종 연산을 끝낸 후 옵셔널 객체가 비어있다면 예외 공급자 함수를 통해 예외를 발생시킨다.

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X;

// Example
Optional.ofNullable("input").filter("test"::equals).orElseThrow(NoSuchElementException::new);

 

🙋‍♂️ orElse, orElseGet 의 차이

orElse는 옵셔널 객체가 비어있든 비어있지 않든 반드시 실행되며 매개변수는 객체이다.

Optional.ofNullable(something).orElse(new Something());

 

orElseGet는 옵셔널 객체가 비어있을 때 실행되며 매개변수는 공급자 함수 Supplier이다. 따라서 기본값을 제공할 때 사용할 비즈니스 로직을 메서드 안에서 구현할 수 있다

Optional.ofNullable(something).orElse(new Something());

Optional.ofNullable(something).orElseGet(() -> {
	// business logic ...
	return value;
});

 

 

🙋‍♂️ Optional<List<T>> vs List<T>

List를 Optional로 감싸게 되면 Optional로 체크하고 내부의 List 도 체크해야 하는 등 코드의 가독성이 나빠진다.

따라서 List를 사용할 때는 아래의 코드처럼 List를 채워주는 것이 좋다.

List data = Optional.ofNullable(somethingList).orElse(Collections.emptyList());
if (!data.isEmpty()) {
	// do something...
}

 

 

 

4️⃣ Java 9

자바 9에서 아래와 같은 옵셔널 메서드가 추가되었다.

 

4.1. or

중간 처리 메서드로 기본값을 제공할 수 있는 공급자 함수를 정의할 수 있다.

기존의 orElseGet()과 유사하지만 중간에 체이닝을 통해 우선순위를 결정할 수 있다는 것이 차이점이다.

or() 연산 중에 비어있는 옵셔널이 된다면 다음 or() 메서드를 진행한다.

public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier);

// Example
String result = Optional.ofNullable("test")
        .filter(value -> "filter".equals(value))
        .or(Optional::empty)
        .or(() -> Optional.of("second"))
        .orElse("final");
System.out.println(result); // print 'second'

 

4.2. ifPresentOrElse

ifPresentOrElse()는 종단 처리 메서드이다. 기존의 ifPresent()와 유사하지만 한 가지 매개변수가 추가되었다.

첫 번째 매개변수인 action은 유효한 객체를 받을 경우 실행하고, 두 번째 매개변수인 emptyAction은 유효한 객체를 받지 못한 경우 실행한다.

public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction);

// Example
Optional.ofNullable("test")
    .ifPresentOrElse(value -> System.out.println(value), () -> System.out.println("null")); // print 'test'
Optional.ofNullable(null)
    .ifPresentOrElse(value -> System.out.println(value), () -> System.out.println("null")); // print 'null'

 

4.3. stream

stream() 은 중간 처리 연산자로 자바 8에서 옵셔널 객체가 바로 스트림 객체로 전환되지 않았던 부분을 해결하였다.

아래 예제는 리스트에서 일부 값만 추출하여 스트림으로 변환한 후 다시 리스트로 수집하는 과정이다.

public Stream<T> stream();

// Example
List<String> result = List.of(1, 2, 3, 4)
    .stream()
    .map(val -> val % 2 == 0 ? Optional.of(val) : Optional.empty())
    .flatMap(Optional::stream)
    .map(String::valueOf)
    .collect(Collectors.toList());
System.out.println(result); // print '[2, 4]'

 

 

 

5️⃣ Java 10

자바 10에서 아래와 같은 옵셔널 메서드가 추가되었다.

 

5.1. orElseThrow

매개변수가 필요 없는 예외 메서드이며, 기존 코드를 간결하게 표현할 수 있다.

public T orElseThrow();

// Example(JAVA 8)
Optional.ofNullable(something).orElseThrow(NoSuchElementException::new);

// Example(JAVA 10)
Optional.ofNullable(something).orElseThrow();

 

 

 

 

📗 정리

옵셔널은 복잡한 예외처리 과정을 짧게 표현하도록 돕는다.

기존 과정을 옵셔널로 짧게 표현할 수 있으니 앞으로는 Optional만 사용하려는 생각이 들 수도 있다.

하지만 경우에 따라서 기존의 예외처리를 위한 분기문도 적절하게 사용하는 것이 코드 가독성을 더 좋게 만들 수도 있다.

따라서 상황에 따라 적절한 코드를 작성할 수 있는 능력을 기르는 것이 중요하다.

 

▪ Optional<T>는 null이 아닌 T 타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다. 

▪ 아무것도 담지 않은 옵셔널은 '비었다'라고 말한다. 반대로, 어떤 값을 담은 옵셔널은 '비지 않았다'라고 한다. 

▪ 옵셔널은 원소를 최대 한 개만 가질 수 있는 불변 컬렉션이다. 

▪ 보통은 T를 반환하지만, 특정 조건에서 아무것도 반환하지 않아야 할 때 T 대신 Optional<T>를 반환하도록 선언하면, 유효한 반환 값이 없을 때 빈 결과를 반환하는 메서드를 만들 수 있다. 

▪ 옵셔널을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉬우며, null을 반환하는 메서드보다 오류 가능성이 적다.

▪ 옵셔널 클래스는 기본형을 제외하고 OptionalInt, OptionalLong, OptionalDouble 클래스를 별도로 제공한다.

 

 

 

 

 

 

 

🔅 참고사이트

https://jdm.kr/blog/234

 

 

 

 

 

 

 

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기