public final class Optional<T> {
// null 이 아닌 경우, 값이 존재 O
// null 인 경우, 값이 존재 X
private final T value;
...
}
Optional<T>
null 이 올 수 있는 값을 감싸는 Wrapper 클래스
value 에 값을 저장하기 때문에, 참조할 때 값이 null 이더라도 바로 NPE(NullPointerException)가 발생 X
코드가 Null-Safe 해진다. (= 예상치 못한 null 에 대응 가능해진다.(null 그 자체를 의미하는 것이 아님!))
객체가 null 인지 아닌지 판별하기 위해 사용
잘못된 사용
// 단순히 값을 얻으려고 Optional을 사용한다.
public String findUserName(long id) {
String name = ... ;
return Optional.ofNullable(name).orElse("Default");
}
메소드의 반환 값이 null 일 경우,
오버헤드(대체하는 함수를 호출하는 등...)로 인해 시스템 성능의 저하 위험이 있으므로, Optional을 사용하지 않는 것이 좋다.
다음과 같은 경우에 반환값으로만 사용하자.
오버헤드
- 프로그램의 실행흐름 도중에 동떨어진 위치의 코드를 실행시켜야 할 때, 추가적으로 시간, 메모리, 자원이 사용되는 현상
- 특히, 외부 함수를 사용할 때 나타난다
public class User implements Serializable {
private Optional<String> name;
}
// 잘못된 코드
public class User {
private final String name;
private final Optional<String> postcode;
public Customer(String name, Optional<String> postcode) {
this.name = Objects.requireNonNull(name, () -> "Cannot be null");
this.postcode = postcode;
}
// name을 얻기 위해 Optional.ofNullable()로 반환
// Getter에 Optional을 얹어 반환하는 것은 남용이다.
public Optional<String> getName() {
return Optional.ofNullable(name);
}
public Optional<String> getPostcode() {
return postcode;
}
}
Optional 을 필드 값으로 사용하지 말자.
Optional 을 생성자, 수정자, 메소드 파라미터 등으로 넘기지 말자.
optionalUser의 값이 비어있으면 NoSuchElementException가 발생할 수 있으므로, 값의 유무를 검사하고 꺼낸다.
public void temp(Optional<User> optionalUser) {
User user = optionalUser.orElseThrow(IllegalStateException::new);
// 이후의 후처리 작업 진행...
}
그러나, optionalUser 객체 자체가 null 일 경우에는 불필요하게 코드 글자수를 늘려야만 한다.
public void temp(Optional<User> optionalUser) {
if (optionalUser != null && optionalUser.isPresent()) {
// 이후의 후처리 작업 진행...
}
throw new IllegalStateException();
}
Optional 안에 있는 객체를 얻기 위해서는 Optional 객체를 통해 접근해야 하므로, 접근 비용이 증가
Optional 은 객체를 감싸는 컨테이너이므로, Optional 객체 자체를 저장하기 위한 메모리가 추가로 필요
get()
비어있는 optional 객체 반환
empty()
optional 객체를 null 로 초기화
isPresent()
optional 객체에 저장된 값이 null 인지 확인
orElse()
저장된 값이 존재하면, 해당 값을 반환
값이 존재하지 않으면, '파라미터의 값을 반환'
orElseGet()
저장된 값이 존재하면, 해당 값을 반환
값이 존재하지 않으면, '파라미터의 람다식 결과값을 반환'
orElseThrow()
저장된 값이 존재하면, 해당 값을 반환
값이 존재하지 않으면, '파라미터의 예외를 발생'시킴
Optional<User> optionalUser = ... ;
// optional이 갖는 value가 없으면 NoSuchElementException 발생
User user = optionalUser.get();
public Optional<Cart> fetchCart() {
Optional<Cart> emptyCart = null;
...
}
값이 없는 경우라면 Optional.empty() 로 초기화하자.
Optional<String> optional = Optional.empty();
System.out.println(optional); // Optional.empty
System.out.println(optional.isPresent()); // false
Optional.empty()
를 사용하자.
public final class Optional<T> {
// Optional 클래스는 내부에서 static 변수로 EMPTY 객체를 미리 생성해서 가지고 있다.
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
...
}
// Optional의 value는 절대 null이 아니다.
Optional<String> optional = Optional.of("MyName");
값이 있는 경우(= 어떤 데이터가 절대 null 이 아니라면), Optional.of() 로 생성 가능하다.
Optional.of() 로 Null을 저장하려고 하면, NPE(NullPointerException) 가 발생
// Optional의 value는 값이 있을 수도 있고 null 일 수도 있다.
Optional<String> optional = Optional.ofNullable(getName());
String name = optional.orElse("anonymous"); // 값이 없다면 "anonymous" 를 리턴
파라미터로 값을 받는다.
값이 미리 존재하는 경우에 사용
null을 반환해야 하는 경우라면, orElse(null) 을 활용
비용 : orElse > orElseGet
public final class Optional<T> {
... // 생략
// orElse
public T orElse(T other) {
return value != null ? value : other;
}
}
// 값이 비어있을 때, orElse 를 호출
public void findUserEmailOrElse() {
String userEmail = "Empty";
String result = Optional.ofNullable(userEmail) // 1-1) Optional.ofNullable로 "EMPTY"를 갖는 Optional 객체 생성
.orElse(getUserEmail()); // 1-2) getUserEmail()가 실행되어, 반환값을 orElse 파라미터로 전달
System.out.println(result);
}
/* 출력 결과
Empty
*/
public void findByUserEmail(String userEmail) {
return userRepository.findByUserEmail(userEmail)
.orElse(createUserWithEmail(userEmail));
}
파라미터로 함수형 인터페이스(함수)를 받는다.
값이 미리 존재하지 않는 경우에 사용(거의 대부분의 경우)
값이 없어서 throw해야하는 경우라면, orElseThrow를 사용
public final class Optional<T> {
... // 생략
// orElseGet
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
}
// 값이 비어있을 때, orElseGet 을 호출
public void findUserEmailOrElseGet() {
String userEmail = "Empty";
String result = Optional.ofNullable(userEmail) // 2-1) Optional.ofNullable로 "EMPTY"를 갖는 Optional 객체 생성
.orElseGet(this::getUserEmail); // 2-2) getUserEmail() 함수 자체를 orElseGet 파라미터로 전달
System.out.println(result);
}
/* 출력 결과
Empty
*/
// 값이 비어있을 때, orElseGet 을 호출 + Optional의 값으로 null이 있다면
public void findUserEmailOrElseGet() {
String result = Optional.ofNullable(null) // 3-1) Optional.ofNullable로 null를 갖는 Optional 객체 생성
.orElseGet(this::getUserEmail); // 3-2) getUserEmail() 함수 자체를 orElseGet 파라미터로 전달
System.out.println(result);
}
private String getUserEmail() {
System.out.println("getUserEmail() Called");
return "mangkyu@tistory.com";
}
/* 출력 결과
Empty
*/
public void findByUserEmail(String userEmail) {
return userRepository.findByUserEmail(userEmail)
.orElseGet(createUserWithEmail(userEmail));
}
private String createUserWithEmail(String userEmail) {
User newUser = new User(userEmail);
return userRepository.save(newUser);
}
Optional<T>
와 LambdaList<String> nameList = Optional.ofNullable(getNames())
.orElseGet(() -> new ArrayList<>());
보다 간단히 표현 가능
잘못된 코드
public Optional<List<User>> getUserList() {
List<User> userList = ...; // null이 올 수 있음
return Optional.ofNullable(items);
}
올바른 코드
public List<User> getUserList() {
List<User> userList = ...; // null이 올 수 있음
return items == null
? Collections.emptyList()
: userList;
}
잘못된 코드
public Map<String, Optional<String>> getUserNameMap() {
Map<String, Optional<String>> items = new HashMap<>();
items.put("I1", Optional.ofNullable(...));
items.put("I2", Optional.ofNullable(...));
Optional<String> item = items.get("I1");
if (item == null) {
return "Default Name"
} else {
return item.orElse("Default Name");
}
}
올바른 코드
public Map<String, String> getUserNameMap() {
Map<String, String> items = new HashMap<>();
items.put("I1", ...);
items.put("I2", ...);
return items.getOrDefault("I1", "Default Name");
}
참고: [Java] Optional이란? Optional 개념 및 사용법 - (1/2)
참고: [Java] 언제 Optional을 사용해야 하는가? 올바른 Optional 사용법 가이드 - (2/2)
참고: Flutter의 null safety 이해하기
참고: [Java] Optional, Optional의 메서드, Optional 사용시 주의사항