🧐 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
을 방지하고 코드의 가독성을 높이는데 도움을 줍니다.
그러나 잘못된 사용은 오히려 큰 부작용을 낳을 수 있기 때문에 올바른 사용법을 익히는 것이 중요합니다.