SpringBoot 블로그 만들기 - Optional 클래스

정원·2022년 12월 19일
0

SpringBoot

목록 보기
10/34

22.12.19 Optional 클래스

java.util.Optional<T> 클래스

Optional<T> 클래스는 Integer나 Double 클래스처럼 'T'타입의 객체를 포장해 주는
래퍼 클래스(Wrapper class)입니다.
따라서 Optional 인스턴스는 모든 타입의 참조 변수를 저장할 수 있습니다.

이러한 Optional 객체를 사용하면 예상치 못한 NullPointerException 예외를 제공되는 메소드로 간단히 회피할 수 있습니다.

즉, 복잡한 조건문 없이도 널(null) 값으로 인해 발생하는 예외를 처리할 수 있게 됩니다.

Optional 활용하기

[ Optional 생성하기 ]

  • Optional.empty() - 값이 Null인 경우
    Optional은 Wrapper 클래스이기 때문에 값이 없을 수도 있는데, 이때는 Optional.empty()로 생성할 수 있다.
Optional<String> optional = Optional.empty();

System.out.println(optional); // Optional.empty
System.out.println(optional.isPresent()); // false
  • Optional.of() - 값이 Null이 아닌 경우
    만약 어떤 데이터가 절대 null이 아니라면 Optional.of()로 생성할 수 있다. 만약 Optional.of()로 Null을 저장하려고 하면 NullPointerException이 발생한다.
// Optional의 value는 절대 null이 아니다.
Optional<String> optional = Optional.of("MyName");
  • Optional.ofNullbale() - 값이 Null일수도, 아닐수도 있는 경우
    만약 어떤 데이터가 null이 올 수도 있고 아닐 수도 있는 경우에는 Optional.ofNullbale로 생성할 수 있다. 그리고 이후에 orElse 또는 orElseGet 메소드를 이용해서 값이 없는 경우라도 안전하게 값을 가져올 수 있다.
// Optional의 value는 값이 있을 수도 있고 null 일 수도 있다.
Optional<String> optional = Optional.ofNullable(getName());
String name = optional.orElse("anonymous"); // 값이 없다면 "anonymous" 를 리턴

[ Optional 정리 ]
Optional은 null 또는 값을 감싸서 NPE(NullPointerException)로부터 부담을 줄이기 위해 등장한 Wrapper 클래스이다. Optional은 값을 Wrapping하고 다시 풀고, null 일 경우에는 대체하는 함수를 호출하는 등의 오버헤드가 있으므로 잘못 사용하면 시스템 성능이 저하된다. 그렇기 때문에 메소드의 반환 값이 절대 null이 아니라면 Optional을 사용하지 않는 것이 좋다. 즉, Optional은 메소드의 결과가 null이 될 수 있으며, null에 의해 오류가 발생할 가능성이 매우 높을 때 반환값으로만 사용되어야 한다. 또한 Optional은 파라미터로 넘어가는 등이 아니라 반환 타입으로써 제한적으로 사용되도록 설계되었다.

Optional의 orElse와 orElseGet 차이

[orElse와 orElseGet의 차이]
Optional API의 단말 연산에는 orElse와 orElseGet 함수가 있다. 비슷해 보이는 두 함수는 엄청난 차이가 있다.

  • orElse: 파라미터로 값을 받는다.
  • orElseGet: 파라미터로 함수형 인터페이스(함수)를 받는다.

실제로 Optional 코드를 보면 다음과 orElse와 orElseGet이 각각 구현되어 있음을 확인할 수 있다.

public final class Optional<T> {

    ... // 생략

    public T orElse(T other) {
        return value != null ? value : other;
    }

    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }
    
}

[orElse와 orElseGet의 차이 예시 코드]

public void findUserEmailOrElse() {
    String userEmail = "Empty";
    String result = Optional.ofNullable(userEmail)
    	.orElse(getUserEmail());
        
    System.out.println(result);
}

public void findUserEmailOrElseGet() {
    String userEmail = "Empty";
    String result = Optional.ofNullable(userEmail)
    	.orElseGet(this::getUserEmail);
        
    System.out.println(result);
}

private String getUserEmail() {
    System.out.println("getUserEmail() Called");
    return "mangkyu@tistory.com";
}

위의 함수를 각각 실행해보면 다음과 같은데, 이러한 결과가 발생한 이유를 자세히 살펴보도록 하자.

// 1. orElse인 경우
getUserEmail() Called
Empty

// 2. orElseGet인 경우
Empty

먼저 OrElse인 경우에는 다음과 같은 순서로 처리가 된다.

  1. Optional.ofNullable로 "EMPTY"를 갖는 Optional 객체 생성
  2. getUserEmail()가 실행되어 반환값을 orElse 파라미터로 전달
  3. orElse가 호출됨, "EMPTY"가 Null이 아니므로 "EMPTY"를 그대로 가짐

위와 같이 동작하는 이유는 Optional.orElse()가 값을 파라미터로 받고, orElse 파라미터로 값을 넘겨주기 위해 getUserEmail()이 호출되었기 때문이다. 하지만 함수형 인터페이스(함수)를 파라미터로 받는 orElseGet에서는 동작이 달라진다.

  1. Optional.ofNullable로 "EMPTY"를 갖는 Optional 객체 생성
  2. getUserEmail() 함수 자체를 orElseGet 파라미터로 전달
  3. orElseGet이 호출됨, "EMPTY"가 Null이 아니므로 "EMPTY"를 그대로 가지며 getUserEmail()이 호출되지 않음

orElseGet에서는 파라미터로 넘어간 값인 getUserEmail 함수가 Null이 아니므로 .get에 의해 함수가 호출되지 않는다. 만약 Optional의 값으로 null이 있다면, 다음과 같은 흐름에 의해 orElseGet의 파라미터로 넘어온 getUserEmail()이 실행될 것이다.

public void findUserEmailOrElseGet() {
    String result = Optional.ofNullable(null)
    	.orElseGet(this::getUserEmail);
        
    System.out.println(result);
}

private String getUserEmail() {
    System.out.println("getUserEmail() Called");
    return "mangkyu@tistory.com";
} 
  1. Optional.ofNullable로 null를 갖는 Optional 객체 생성
  2. getUserEmail() 자체를 orElseGet 파라미터로 전달
  3. orElseGet이 호출됨, 값이 Null이므로 other.get()이 호출되어 getUserEmail()가 호출됨

orElse와 orElseGet의 차이점 및 사용법 정리

  • orElse
    파라미터로 값을 필요로한다.
    값이 미리 존재하는 경우에 사용한다.
  • orElseGet
    파라미터로 함수(함수형 인터페이스)를 필요로 한다.
    값이 미리 존재하지 않는 거의 대부분의 경우에 orElseGet을 사용하면 된다.

Optional이 제공하는 메소드

  • filter()
    filter 메소드의 인자인 람다식이 true이면 Optional 객체를 그대로 통과시키고, false이면 Optional.empty()를 리턴하여 추가로 처리가 되지 않도록 한다.

    String result1 = Optional.of("ABCDE").filter((val) -> val.contains("ABC")).orElse("Does not contain ABC");
    System.out.println(result1); //ABCDE
    
    String result2 = Optional.of("CDEFG").filter((val) -> val.contains("ABC")).orElse("Does not contain ABC");
    System.out.println(result2); //Does not contain ABC
  • map()
    입력받은 값을 다른 값으로 변환하는 메서드이다.
    String result3 = Optional.of("abcde").map(String::toUpperCase).orElse("fail");
    System.out.println(result3); //ABCDE
  • isPresent()
    값이 있으면 true를 반환하고 그렇지 않으면 false를 반환한다.
  • ifPresent()
    람다식을 인자로 받는데, 값이 존재할 때만 람다식이 적용된다. 값이 존재하지 않으면 실행되지 않는다.

    Optional.of("ABCDE").ifPresent(System.out :: println); //결과 : ABCDE
    
    Optional.ofNullable(null).ifPresent(System.out :: println); //결과 : 아무것도 출력되지 않음
  • get()
    Optional 객체가 가지고 있는 value를 가져온다. 만약 객체에 값이 없다면 NoSuchElementException이 발생한다.
  • orElse()
    Optional 객체가 비어 있다면 orElse() 메소드의 지정된 값으로 리턴된다.
  • orElseGet()
    Optional 객체가 비어 있다면 기본값으로 제공할 supplier를 지정한다. orElse()의 경우 값이 null이든 아니던 호출 되며, orElseGet()은 null일 때만 호출된다.
  • orElseThrow()
    연산을 끝낸 후에도 Optional 객체가 비어 있다면 예외 공급자 함수를 통해 예외를 발생시킨다.

    String result1 = Optional.of("CDEFG").filter((val) -> val.contains("ABC")).orElseThrow(NoSuchElementException::new);
    System.out.println(result1);
    
    //결과
    Exception in thread "main" java.util.NoSuchElementException
        at java.base/java.util.Optional.orElseThrow(Optional.java:385)

0개의 댓글