[Java] Optional 클래스 살펴보기 (2)

2024. 1. 24. 10:37·Java

이전에 다뤘듯이, Optional 의 핵심은 메소드가 null을 리턴하는 상황을 없애는데 있습니다. 따라서 메소드가 null을 직접적으로 리턴하는 대신, 반환하는 값이 null일 가능성이 있음을 나타내는 Optional 클래스를 이용해 이를 표현합니다. 

 

[Java] Optional 클래스의 등장 (1)

 

이번 포스팅에서는 이에 나아가, Optional 클래스가 null 대신 어떻게 값이 없는 상황을 어떻게 모델링하는지에 대해 살펴보도록 하겠습니다. 

 

1.  Optional 객체 생성

2. Optional 객체 API

3. Optional과 스트림

4. 예제

 

 

Optional 클래스는 선택형 값을 캡슐화 하는 클래스입니다. 값이 있으면 Optional 클래스는 그 값을 감싸고, 없다면 Optional.empty() 로 빈 Optional 을 반환하는 일종의 Wrapper 클래스입니다. null이 아닌 Optional 클래스를 반환함으로써 Optional 클래스가 제공하는 다양한 API를 제공함과 동시에 NullPointerException을 방지하게 됩니다. 

 

이제, Optional 클래스가 어떤 API를 제공하는지 살펴보도록 하겠습니다. 

 

 

1. Optional 객체 생성

먼저 Optional 객체를 생성하는 인터페이스 입니다. 

public final class Optional<T> {
    /**
     * Common instance for {@code empty()}.
     */
    private static final Optional<?> EMPTY = new Optional<>(null);
    
    // ...
    
    
    // 1. 빈 Optional 객체를 생성하는 방법 (즉, 이전의 null을 표현할 Optional 객체 생성)
     public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
    
    // 2. 주어지는 value 파라미터가 null 이면 즉시 예외가 발생하도록 하며, 아닐 시 Optional<T> 객체 생성
    public static <T> Optional<T> of(T value) {
        return new Optional<>(Objects.requireNonNull(value));
    }
    
    // 3. value가 널이면 빈 Optional 생성, 아닐 시 value를 감싼 Optional<T> 생성
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? (Optional<T>) EMPTY
                             : new Optional<>(value);
    }

Optional NullPointerException을 발생시키는 두번째 방법에 대해 의문을 품으실 수 있습니다. 하지만, 이는 에러 발생 시점을 명확히 할 수 있다는데 의의가 있습니다. (이를 사용하지 않았다면, 반환된 null을 참조하려는 시점에 NullPointerException이 발생하여 유지보수를 어렵게 만들었을것 입니다)

 

Optional.empty()는 정적 팩토리 메소드로 구현되며 싱글톤으로 구현됩니다. 따라서 모든 Optional.empty()호출은 동일한 인스턴스를 반환합니다.

 

 

Optional 객체 API

이렇게 반환된 Optional 객체를 사용해 Optional로부터 객체를 얻는 API는 다음과 같이 제공됩니다.

public final class Optional<T> {

	// ...
    
    // 1. 가장 안전하지 않은 방법입니다. value가 없다면 예외를 던지게 됩니다.
    public T get() {
       	if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    
    // 2. 빈 Optional 일시, 다른 객체를 리턴하도록 합니다.
    public T orElse(T other) {
        return value != null ? value : other;
    }

	// 3. 빈 Optional 일시, 파라미터로 주어진 supplier 함수형 인터페이스를 통해 생성합니다.
    public T orElseGet(Supplier<? extends T> supplier) {
        return value != null ? value : supplier.get();
    }
    
    // --------------------메소드 참조---------------------------
    
    // 1. 전달받은 메소드를 실행합니다. 
    public void ifPresent(Consumer<? super T> action) {
        if (value != null) {
            action.accept(value);
        }
    }
    
    // 2. 위 와 달리, value == null 일 때, Runnable 인터페이스를 통해 스레드를 실행시킨다.
    public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
        if (value != null) {
            action.accept(value);
        } else {
            emptyAction.run();
        }
    }    

    
    
}

 

뿐만 아니라, 아래와 같이 사용자가 정의하는 예외를 던지도록 구성할 수 있습니다. 

public final class Optional<T> {

	// ...
    
    // 1. get() 과 같은 방식으로 동작합니다. 
    public T orElseThrow() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    
    // 2. NoSuchElementException 대신 다른 에러를 던질 수 있도록 합니다. ( Lazy 적용 )
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
}

 

 

 

 

Optional과 스트림

위에서 Optional 이 제공하는 여러 유용한 API를 살펴 봤습니다. 분명히 유용한 API 들이지만, Optional 객체를 처리하는 코드를 메소드내에 지저분하게 작성하는 단점이 여전히 존재했습니다. 자바9 부터 Optional에 Stream을 이용하여 명시적인 검사를 제거할 수 있게 됐습니다. 

public final class Optional<T> {

	// ...

    public Stream<T> stream() {
        if (!isPresent()) {
            return Stream.empty();
        } else {
            return Stream.of(value);
        }
    }
    
    // ...

}

스트림을 반환하는 위 API를 이용하여 Optional 객체를 반환하는 메소드를 사용하는 코드를 다음과 같이 간결하게 작성할 수 있게 됩니다.

 

 

 

예제

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        
        // person의 car, 그리고 car의 insurance의 name을 안전하게 접근하고 출력
        String insuranceName = person.getCar()
                                     .flatMap(Car::getInsurance)
                                     .flatMap(Insurance::getName)
                                     .orElse("Unknown"); // Optional이 비어있다면 "Unknown" 반환
        System.out.println(insuranceName);
    }
}



class Person {
    private Car car;
    
    public Optional<Car> getCar() { // Optional을 통해 리턴값이 null일 수 있다는 것을 명시합니다.
        return Optional.ofNullable(this.car);
    }
}

class Car {
    private Insurance insurance;
    
    public Optional<Insurance> getInsurance() { // Optional을 통해 리턴값이 null일 수 있다는 것을 명시합니다.
        return Optional.ofNullable(insurance);
    }
}

class Insurance {
    private String name;
    
    public Optional<String> getName() { // Optional을 통해 리턴값이 null일 수 있다는 것을 명시합니다.
        return Optional.ofNullable(name);
    }
}

 

 

이렇게 Optional의 등장배경, 사용방법에 대해 전부 알아봤습니다. 

 

 

Optional 을 반환하는 메소드들을 정의하는 것은 이 메소드를 사용하는 모든 사람들에게 이 메소드가 빈값을 리턴할 수 있음을 잘 문서화해서 제공하는 것과 같습니다. 협업에서 특히나 유용하게 사용될 것 같으며, 이를 적극적으로 사용하도록 노력해 보도록 하겠습니다.

 

긴 글 읽어주셔서 감사합니다. 

'Java' 카테고리의 다른 글

Java IO / NIO  (0) 2024.12.16
[Java] Optional 클래스의 등장 (1)  (2) 2024.01.23
[Java] 제네릭 - 와일드 카드  (0) 2024.01.15
[Java] Collection Framework  (1) 2024.01.10
[Java] 제네릭의 기초  (1) 2024.01.04
'Java' 카테고리의 다른 글
  • Java IO / NIO
  • [Java] Optional 클래스의 등장 (1)
  • [Java] 제네릭 - 와일드 카드
  • [Java] Collection Framework
윤희종
윤희종
호기심을 잃지 말자 지적, 질문은 언제나 환영합니다 ;)
  • 윤희종
    서버견문록
    윤희종
  • 전체
    오늘
    어제
    • 분류 전체보기 (36)
      • 데일리 플랜 (1)
      • 이것저것 (4)
      • Java (6)
      • Spring (12)
        • SpringBoot (10)
        • Spring MVC (0)
      • Computer Science (4)
        • Network (1)
        • Operating System (0)
        • Data Structure (0)
        • Algorithm (2)
        • Database (0)
      • IOS (0)
      • 프로그래머스 문제풀이 (2)
      • 프로젝트 일기 (7)
        • 한편의 수학 학원 (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    cache write back
    스프링 부트
    인증 우회 테스트
    인증 테스트
    제네릭
    스프링
    SecurityFilterChain
    비동기 처리 유의점
    read through
    알고리즘
    스프링 부트 인증 우회
    servlet
    springboot
    Spring
    스프링 시큐리티 사용법
    SecurityFilterChain 구성
    캐시
    성능 개선
    mysql 쿼리 최적화
    스프링 시큐리티 구성
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
윤희종
[Java] Optional 클래스 살펴보기 (2)
상단으로

티스토리툴바