[ JAVA ] Optional

Wooju Kang ·2025년 7월 6일
post-thumbnail

GIF 출처 : https://www.amigoscode.com/courses/java

🖥 Contents


1 ) Optional 이란?

2 ) Optional 메소드

3 ) 원시 타입 Optional




1 ) Optional 이란?


Optional 이란 ?

: java 8 부터 도입된 타입으로 null을 일관성 있게 처리하기 위한 전문 타입이다. 쉽게 말해 ' null이 될 수 있는 값을 담고 있는 상자 ' 라고 생각하면 된다.

도입 배경

: 먼저 자바에서는 클래스나 인터페이스 , 배열과 같은 비원시 타입은 값이 할당되지 않을경우 기본값으로 null 을 갖게 된다.

이때 , null참조에 접근하려고 시도하면 JVM은 NullPointException을 발생시키고 적절히 처리하지 않으면 해당 스레드는 중단된다. 그 이유는 바로 null 상황에서 예상되거나 적합하거나 허용되는 값이 아니기 때문이다.

또한 null은 값의 부재를 나타내는 타입 모호성의 특징을 지니고 있다. 따라서 null 문제를 해결하기 위해 전통적으로 세 가지 방법을 통해 null을 다루었다.

전통적인 Null관리법

① 모범 사례
② 도구를 활용한 null 검사
Optional과 유사한 특수 타입

( 1 ) 모범 사례

프로그래밍 언어에서 내장된 null 처리 방식을 제공하지 않는 경우 코드를 null에 안전하게 만들기 위해 모범 사례비공식적인 규칙을 통해 이를 처리했다.

1. null 값을 전달하거나 수용 또는 반환하지 않아야 한다.

: 메서드나 생성자의 오버로딩을 통해 필요 없는 인수가 null값을 가지는 것을 방지할 수 있다.

2. 통제할 수 없는 모든 것을 확인하기

: 서드파티 코드 혹은 다른이들의 코드를 확인할 때 , 문서에 명시되어있지 않다면 항상 null일 가능성이 존재하므로 꼼꼼하게 검사해야한다.

3. null은 내부 구현에서는 허용된다.

: 내부 로직 구성에 있어 null값을 반환하지 않는 이상 자유롭게 활용해도 된다.

( 2 ) 도구를 활용한 null 검사

: 도구를 활용한 검사는 서드파티 도구 즉, 외부 라이브러리나 검사 프레임워크를 도입하는 것이다. 자바의 경우 어노테이션을 사용하여 null에 대한 처리를
지원하고 있다.

@Nullable : null 타입을 허용하는 어노테이션

@NonNull : null 타입을 허용하지 않는 어노테이션

ex )

interface example { 

   @NonNull
   List<@Nullable String> getListOfNullableStrings();
   
   @Nullable
   List<@NonNull String> getNullableListOfNonNullStrings();
   
   void doWork(@Nullable String indentifier);

}

( 3 ) Optional과 유사한 특수 타입

: Optional도입 전 2011년부터 null처리를 위한 다양한 라이브러리가 사용되었다. 예를 들어 Google Guava 프레임워크에서는 기본적으로 Optional타입을 제공하였다. 이를 통해 null 검사를 제공하는 방식보다는 전문화된 타입을 사용하여 런타임에서 안전하게 처리할 수 있게 되었다.

Optional의 특징

:OptionalStream과 달리 파이프라인에 최종 연산이 추가되기 전까지는 느긋하게 연결되지 않는다 ( Eager ) .

이는 모든 연산은 호출되는 즉시 수행된다는 것이며 만약 null 값이 발견되면 연산의 수와 관계없이 수행되는 작업은 최소화된다.

Optional의 고려사항

: Optional은 값을 기반으로 하는 타입인 특성에서 비롯되며 내부의 값에 집중한다.

때문에 Optional로 감싼 내부 값을 비교하기 위해서는 먼저 컨테이너 ( Optional ) 을 벗겨낸 뒤 내부의 값을 꺼내 비교해야한다. 이때 , == 를 사용하게 되면 서로 다른 객체이기 때문에 false를 반환하게 된다.

따라서 내부의 값을 비교할 때는 equals 메소드를 통해 비교해야한다.




2 ) Optional 메소드


Optional 생성하기

1. Optional.ofNullable(T value)
: 값이 null일 가능성이 있는 경우에 해당 메소드를 통해 null 값을 허용하는 새로운 인스턴스를 생성한다.

ex )

String value = "Woojuices velog";
Optional<String> optionalValue = Optional.ofNullable(value);

2. Optional.of(T value)
: 값이 null인지 확인하고 그렇지 않으면 NullPointerException을 발생시킨다.

ex )

var value = "Woojuices velog";
Optional<String> hasValue = Optional.of(value);

var value2 = null;
Optional<String> exampleOptional = Optional.of(value2);
// NullPointerException 발생 

3. Optional.empty()
: 값이 비어있는 경우를 체크한다.

ex )

Optional<String> value = Optional.empty();

값 확인하고 대응하기

  1. boolean isPresent()
    : 값이 존재할 경우 true를 반환한다.

ex )

Optional<String> say = Optional.of("Woojuice");

System.out.println(say.isPresent()); // true 

2. boolean isEmpty()
: 값이 비어있을 경우 true를 반환한다.

ex )

System.out.println(say.isEmpty()); // false

3. void ifPresent(Consumer<? super T> action)
: 값이 존재할 때 , 특정 로직을 실행한다.

ex )

Optional<String> isHere = "woojuice";
isHere.ifPresent(System.out::println);

4. void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
: 값이 존재할떄와 비어있을 때 , 각각의 로직을 실행한다.

ex )

isHere.ifPresentOrElse(System.out::println
                      ,() -> System.out.println("No value");



값 가져오기

1. T orElse(T other)
: 값이 존재하면 해당 값을 반환하고 그렇지 않으면 other를 반환한다.

ex )

Optional<String> opt = Optional.ofNullable(null);
String result = opt.orElse("기본값");

System.out.println(result);  // "기본값"

2. T orElseGet(Supplier<? extends T> supplier)
: 값이 존재하면 해당 값을 반환하고 , 없으면 람다를 통해 값을 생성해서 반환한다.

ex )

Optional<String> opt = Optional.ofNullable(null);
String result = opt.orElseGet(() -> "예외처리");

System.out.println(result);  // "예외처리"

⭐️ 3. <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
: 값이 존재하지 않을 경우 지정한 예외를 던진다.

ex )

User user = userRepository.findById(id)
                          .orElseThrow(()-> new CustomException("해당 회원은 존재하지 않습니다.",401));

4. T orElseThrow()
: 값이 존재하지 않을 경우 NoSuchElementException을 던진다.

ex )

Optional<String> opt = Optional.ofNullable(null);
String result = opt.orElseThrow();  // NoSuchElementException 발생



스트림 활용하기

: Optional 타입 또한 스트림 요소에 활용될 수 있으며 Optional 타입의 메소드인 isPresent() , isEmpty() 등을 통해 요소 평탄화 , 선택 및 축약등에 활용할 수 있다 .

ex )

List<Permissions> permissions = ...;

List<User> activeUsers = permissions.stream()
                                    .filter(Predicate.not(Permissions::isEmpty))
                                    .map(Permissions::group) 
                                    .filter(Optional::isPresent) // 요소값이 존재하는지 확인 
                                    .map(Optional::get) // Optional 벗겨내서 User객체로 변호나         
                                    .collect(Collectors.toList());



3 ) Optional 원시 타입


: Optional은 기본 자료형에서 표현할 수 없는 ' 아무것도 없는 상태 ' 를 나타낸다. 그러나 Optional은 제네릭 타입으로 사용될 수 없기 때문에 원시 타입과 함께 사용하는 것이 불가능하다.

따라서 원시 타입을 객체로 변환하는 전통적인 방식인 오토 박싱Optional에서 제공하는 특수화된 타입을 사용해야한다.

그러나 앞선 포스팅에서 언급했듯이 오토박싱은 많은 오버헤드를 초래할 가능성이 있기 때문에 이를 방지하기 위한 특수한 타입을 사용하는것을 권장한다.

타입설명
OptionalIntint 값을 감싸는 Optional
OptionalLonglong 값을 감싸는 Optional
OptionalDoubledouble 값을 감싸는 Optional

ex )

OptionalInt opt = OptionalInt.of(42);

if (opt.isPresent()) {
    int value = opt.getAsInt(); // get()이 아니라 getAsInt()
    System.out.println("값: " + value);
} else {
    System.out.println("값이 없습니다.");
}

profile
배겐드 📡

0개의 댓글