Language/Java

[Java] Optional

by Donghwan 2021. 10. 29.

공식 API 문서에서는 메서드가 반환할 결과값이 ‘없음’을 명백하게 표현할 필요가 있고, null을 반환하면 에러를 유발할 가능성이 높은 상황에서 메서드의 반환 타입으로 Optional을 사용하자는 것이 Optional을 만든 주된 목적입니다. Optional 타입의 변수의 값은 절대 null이어서는 안 되며, 항상 Optional 인스턴스를 가리켜야 한다고 말하고 있습니다.

Optional 클래스는 Integer나 Double 클래스처럼 'T'타입의 객체를 포장해 주는 래퍼 클래스(Wrapper class)입니다. 따라서 Optional 인스턴스는 모든 타입의 참조 변수를 저장할 수 있습니다. 이러한 Optional 객체를 사용하면 예상치 못한 NullPointerException 예외를 제공되는 메서드로 간단히 회피할 수 있습니다.

 

Optional의 생성

Optional.of(Value)
of() 메서드는 null이 아닌 명시된 값을 가지는 Optional 객체를 반환합니다. 만약 of() 메서드를 통해 생성된 Optional 객체에 null이 저장되면 NullPointerException 예외가 발생합니다. Optional.of() 메서드를 들여다 보면 Optional T 타입의 value를 받는 private 생성자를 통해 Optional을 생성을 하게 되는데 이때 requiredNonNull을 통해 value의 값을 파악하고 NPE를 발생 시키게 됩니다.
//Optional.of 생성 과정
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}
Optional<String> optionalOf1 = Optional.of("donghwan");

//NPE 발생
Optional<String> optionalOf2 = Optional.of(null);​
Optional.ofNullable(Value)
만약 참조 변수의 값이 만에 하나 null이 될 가능성이 있다면, ofNullable() 메서드를 사용하여 Optional 객체를 생성하는 것이 좋습니다. ofNullable() 메서드소드는 명시된 값이 null이 아니면 명시된 값을 가지는 Optional 객체를 반환하며, 명시된 값이 null이면 비어있는 Optional 객체를 반환합니다. Optional.ofNullable() 메서드를 들여다 보면 value의 여부에 따라 of() 또는 empty() 메서드를 실행하게 됩니다.
//Optional.ofNuallble 생성 과정
public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}
Optional<String> optionalNullable = Optional.ofNullable(null);
Optional.empty()
Empty()의 코드를 보면 바로 기본 생성자로 초기화를 하는 로직을 가지고 있습니다. 따라서 value가 null로 생성이 됩니다. 
//Optional.empty 생성 과정
private static final Optional<?> EMPTY = new Optional<>();

private final T value;

private Optional() {
    this.value = null;
}

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}​

 

Optional 생성에 따른 동일성과 동등성 비교

@Test
@DisplayName("Optional 생성과 동등성과 동일성 비교")
void optionalSimpleTest() {
    //given
    Optional<String> optionalSample1 = Optional.of("");
    Optional<String> optionalSample2 = Optional.empty();
    Optional<String> optionalSample3 = Optional.ofNullable(null);
    
    //sample1과 sample2의 관계
    //전통적인 의미의 empty는 null과 ""를 만족하지만 Optional에선 만족하지 않습니다.
    //empty()로 생성된 Optional은 내부 값을 null을 가집니다.
    assertEquals(optionalSample1.equals(optionalSample2), false);
    assertEquals(optionalSample1 == optionalSample2, false);
    
    //sample1과 sample3의 관계
    //당연히 null과 값이기 때문에 서로 다릅니다.
    assertEquals(optionalSample1.equals(optionalSample3), false);
    assertEquals(optionalSample1 == optionalSample3, false);
    
    //sample2와 sample3의 관계
    //둘 다 내부적으로 value가 null이기 때문에 true를 만족합니다.
    assertEquals(optionalSample2.equals(optionalSample3), true);
    assertEquals(optionalSample2 == optionalSample3, true);
}

 

Optional의 함수

Optional은 get, orElse, orElseGet, orElseThrow, isPresent, ifPresent 등 값에 대한 함수와 filter, map, flatmap 등 stream에 대한 함수를 가지고 있습니다. 

get()
get은 value를 가져오는 함수입니다. value가 null인데 실행할 경우 Exception이 발생하게 됩니다.
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}​
orElse()
orElse는 value가 null일 경우 주입받은 파라미터를 반환합니다.
public T orElse(T other) {
    return value != null ? value : other;
}​
public class OptionalSampleTest {
    String OR_ELSE_MSG = "this is orElse";
    
    @Test
    @DisplayName("orElse 테스트 - Optional Value not null")
    void orElseTest_Success() {
        //given
        Optional<String> optional = Optional.of("donghwan");

        //when
        String result = optional.orElse(OR_ELSE_MSG);

        //then
        assertEquals("donghwan", result);
    }

    @Test
    @DisplayName("orElse 테스트 - Optional Value is null")
    void orElseTest_Fail_ValueIsNull() {
        //given
        Optional<String> optional = Optional.empty();

        //when
        String result = optional.orElse(OR_ELSE_MSG);

        //then
        assertEquals(OR_ELSE_MSG, result);
    }
}
orElseGet()
orElse와 비슷하지만 직접 T 타입의 value를 가지고 있는 orElse와 다르게 Supplier를 파라미터로 받고 있습니다. 따라서 결국 Supplier 실행 결과로 도출된 T 타입의 value를 반환하게 됩니다. 두 함수가 같은 기능을 하지만 왜 구분이 되었는지는 함수 설명을 마친 뒤 하도록 하겠습니다.
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}​
public class OptionalSampleTest {
    String OR_ELSE_GET_MSG = "this is orElseGet";

    @Test
    @DisplayName("orElseGet 테스트 - Optional Value not null")
    void orElseGetTest_Success() {
        //given
        Optional<String> optional = Optional.of("donghwan");

        //when
        String result = optional.orElseGet(() -> OR_ELSE_GET_MSG);

        //then
        assertEquals("donghwan", result);
    }

    @Test
    @DisplayName("orElseGet 테스트 - Optional Value is null")
    void orElseGetTest_Fail_ValueIsNull() {
        //given
        Optional<String> optional = Optional.empty();

        //when
        String result = optional.orElseGet(() -> OR_ELSE_GET_MSG);

        //then
        assertEquals(OR_ELSE_GET_MSG, result);
    }
}​
orElseThrow
orElseThrow는 Throwable을 상속한 타입으로 Exception을 일으키는 역할을 합니다. 보통 사용하게 된다면 RuntimeException 계열을 통해 예외 처리를 하게 됩니다.
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}​
public class OptionalSampleTest {
    @Test
    @DisplayName("orElseThrow 테스트 - Optional Value not null")
    void orElseThrowTest_Success() {
        //given
        Optional<String> optional = Optional.of("donghwan");

        //when
        String result = optional.orElseThrow(() -> new RuntimeException());

        //then
        assertEquals("donghwan", result);
    }

    @Test
    @DisplayName("orElseThrow 테스트 - Optional Value is null")
    void orElseThrowTest_Fail_ValueIsNull() {
        //given
        Optional<String> optional = Optional.empty();

        //when

        //then
        assertThrows(RuntimeException.class, () -> optional.orElseThrow(() -> new RuntimeException()));
    }
}
isPresent
isPresent는 value의 존재 여부에 따라 true, false로 값을 반환해주는 함수입니다.
public boolean isPresent() {
    return value != null;
}​
public class OptionalSampleTest {
    @Test
    @DisplayName("isPresent 테스트 - Optional Value not null")
    void isPresentTest_Success() {
        //given
        Optional<String> optional = Optional.of("donghwan");

        //when
        boolean result = optional.isPresent();

        //then
        assertEquals(true, result);
    }

    @Test
    @DisplayName("isPresent 테스트 - Optional Value is null")
    void isPresentTest_Fail_ValueIsNull() {
        //given
        Optional<String> optional = Optional.empty();

        //when
        boolean result = optional.isPresent();

        //then
        assertEquals(false, result);
    }
}​
ifPresent
ifPresent는 Consumer 타입의 람다를 파라미터로 받은 다음 value가 존재하는 경우에 주입 받은 람다를 실행하는 기능을 합니다.
public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
}​
Optional<String> optional1 = Optional.of("donghwan");
optional1.ifPresent(value -> System.out.println(value));

Optional<String> optional2 = Optional.empty();
optional2.ifPresent(value -> System.out.println(value));

//result
//donghwan​

 


참고자료

728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] 동일성과 동등성 ( == vs equals )  (0) 2021.11.01
[Java] orElse vs orElseGet  (0) 2021.10.29
[Java] 연산자 ( Operator )  (0) 2021.10.23
[Java] 변수 (Variable)  (0) 2021.10.16
[Java] SOLID  (0) 2021.09.29

댓글