Post

자바 Optional의 사용 이유와 주요 기능 간단 정리

🧐 Optional 등장 이유

  • 개발을 할 때 가장 많이 발생하는 예외 중 하나가 NullPointerException 이다.

    NPE 는 컴파일 시점이 아닌 런타임 시점에서 발생하기에 쉽게 발견하기 어렵다.

  • NPE 를 피하기 위해서는 null 여부를 검사해야 하는데, 변수가 많아질수록 검사 로직의 개수는 늘어나고 코드가 지저분해진다.
  • 이를 해결하고자 자바8 부터 Null이나 Null이 아닌 값을 저장하는 컨테이너 클래스인 Optional 이 추가되었다.


[Java 설계자 Brian Goetz의 Optional 정의]

Optional is intended to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.
정리하자면, Optional은 오류가 발생할 수 있는 null을 반환하는 대신 명시적인 결과 없음 을 메서드의 반환 타입으로 사용하기 위한 클래스이다.


🤔 대표적인 Optional 기능 알아보기

[Optional.empty() - 값이 null 인 경우]

  • Optional은 Wrapper 클래스 이므로 값이 없을 수도 있다.
  • 이때는 Optional.empty() 로 빈 Optional을 생성할 수 있다.
1
2
3
4
Optional<String> optional = Optional.empty();

System.out.println(optional); // Optional.empty
System.out.println(optional.isPresent()); // false


[Optional.of() - 값이 Null이 아닌 경우]

  • 어떤 데이터가 절대 null이 아니라면 Optional.of() 로 생성할 수 있다.
  • 만약 null을 저장하려고 하면 NullPointerException 이 발생한다.
1
Optional<String> optional = Optional.of("MyName");


[Optional.ofNullable() - 값이 null일수도, 아닐수도 있는 경우]

  • 둘 다 될 수 있는 경우 Optional.ofNullable() 로 생성할 수 있다.
  • 이후 orElse 또는 orElseGet 메서드를 이용해서 값이 없는 경우라도 안전하게 값을 가져올 수 있다.
1
2
Optional<String> optional = Optional.ofNullable(getName());
String name = optional.orElse("anonymous"); // 값이 없다면 "anonymous" 를 리턴


[Optional 사용 예시]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserService {
    public Optional<User> findUser(String userId) {
        // 사용자 찾기 시도
        User foundUser = findUserInDatabase(userId);

        // 찾은 사용자가 있으면 Optional로 감싸서 반환, 없으면 Optional.empty() 반환
        return Optional.ofNullable(foundUser);
    }

    private User findUserInDatabase(String userId) {
        // 데이터베이스에서 사용자 찾기 로직 (여기선 예시로 null 반환)
        return null;
    }
}

---

// 사용자 ID를 사용하여 사용자 찾기 시도
String userId = "someUserId";
Optional<User> result = userService.findUser(userId);

// 함수형 스타일로 결과 처리
result.ifPresent(user -> System.out.println("User found: " + user.getName()));
result.orElseThrow(() -> new IllegalStateException("User not found"));


🤨 Optional 장단점

[장점]

  • 명시적인 null 처리
    • Optional을 사용하면 메서드가 null을 반환할 수 있는지 여부를 명시적으로 표시할 수 있다.
    • 이는 개발자가 null 처리를 간과할 가능성을 줄여준다.
  • 함수형 프로그래밍 지원
    • Optional 클래스는 map, flatMap, filter 등의 여러 메서드를 제공하여 코드를 간결하게 작성할 수 있다.
  • NullPointerException 방지
    • 올바른 Optional 사용으로 런타임 시점의 NPE 발생을 방지할 수 있다.
  • null 처리 조건문 제거
    • if-else 조건문으로 일일이 null을 체크하지 않고 Optional의 메서드를 사용하여 코드의 가독성을 높이고 유지보수가 쉬워진다.


[단점]

  • 시간적, 공간적 성능 오버헤드 증가
    • Optional은 객체를 감싸는 Wrapper class이므로 저장하기 위한 추가 메모리가 필요하고 접근 비용도 증가한다.
  • 직렬화 불가
    • Optional은 직렬화를 지원하지 않기 때문에, 네트워크 전송이나 디스크 저장을 위해서는 추가적인 처리가 필요하다.
  • 잘못된 사용
    • Optional을 필드 타입으로 사용하거나, 메서드 파라미터로 사용하는 등 잘못된 방식으로 사용하면 코드의 복잡성이 증가한다.


🚨 Optional 사용 주의

  • 만약 Optional을 잘못된 방식으로 남발하여 사용하면 다음과 같은 문제가 발생한다.
    • NullpointerException 대신 NoSuchElementException이 발생함
    • 이전에는 없었던 새로운 문제들이 발생함
    • 코드의 가독성을 떨어뜨림
    • 시간적, 공간적 오버헤드가 증가함


[NullpointerException 대신 NoSuchElementException이 발생]

1
2
3
Optional<Animal> optionalAnimal = ... ;

Animal animal = optionalAnimal.get();   // optional이 갖고 있는 value가 없으면 NoSuchElementException 발생
  • 값이 무조건 있음을 가정하고 로직을 짰지만 값이 없는 경우 NoSuchElementException이 발생한다.


[이전에는 없었던 새로운 문제들이 발생]

1
2
3
4
public class Animal implements Serializable {

    private Optional<String> name;
}
  • 기본적으로 Optional은 직렬화를 지원하지 않기 때문에 캐시나 메세지큐 등과 연동할 때 문제가 발생할 수 있다.

직렬화 과정에서 Optional안의 값이 null인지, 아닌지에 따라 값을 처리하도록 지원하는 라이브러리가 있으나 해당 라이브러리의 스펙을 알아야한다.


[코드의 가독성을 떨어뜨림]

1
2
3
4
5
6
public void test(Optional<Animal> optionalAni) {
    Animal animal = optionalAni.orElseThrow(IllegalStateException::new);

    .
    .
}
  • 위 코드에서 Optional을 받아서 값을 꺼낸다.
  • 값이 비어있을 때를 고려하여 orElseThrow() 로 값의 유무를 검사하고 꺼낸다.


  • 그러나 optionalAni 객체 자체가 null 일 수 있으므로 다음과 같이 추가처리를 해야한다.
1
2
3
4
5
6
7
8
public void test(Optional<Animal> optionalAni) {
    if (optionalAni != null && optionalAni.isPresent()){
        .
        .
    }

    throw new IllegalStateException();
}
  • 이러한 코드는 오히려 값의 유무를 2번 검사하여 단순히 null을 사용했을 때보다 코드가 복잡해지고 가독성이 떨어졌다.


  • 올바르게 사용하려면 메서드 파라미터로 Optional을 넘기지 말아야 한다.
1
2
3
4
5
6
7
8
public void test(Animal animal){
    .
    .
}

// 메서드 호출 방식
Optional<Animal> optionalAni = .. // 값이 있을수도 없을수도 있음
optionalAni.ifPresent(animal -> test(animal));
  • 위와 같이 메서드 파라미터로 Optional을 사용하지 않고, Optional 안에 객체가 있을때만 메서드를 호출하도록 설계했다.


😤 올바른 Optional 사용 규칙

  • Optional 변수에 Null을 할당하지 말것
  • 값이 없을 때는 Optional.orElseX()로 기본 값을 반환할 것
  • 단순히 값을 얻으려는 목적으로 Optional을 사용하지 말 것
  • 생성자, 수정자, 메서드 파라미터 등으로 Optional을 넘기지 말 것
  • Collection의 경우 Optional이 아닌 빈 Collection을 사용할 것
  • 반환 타입으로만 사용할 것


출처

[Java] Optional이란? Optional 개념 및 사용법 - (1/2)

[Java] 언제 Optional을 사용해야 하는가? 올바른 Optional 사용법 가이드 - (2/2)


누군가가 물어본다면

Optional은 null 값을 보다 아름답게 처리하기 위해 도입된 클래스로, NullPointerException을 방지하고 코드의 가독성을 높이는데 도움을 줍니다.

그러나 잘못된 사용은 오히려 큰 부작용을 낳을 수 있기 때문에 올바른 사용법을 익히는 것이 중요합니다.
This post is licensed under CC BY 4.0 by the author.