| 작성자 | 김성민 |
|---|
Java 언어 설계자인 Brian Goetz는 Optional을 만든 의도를 다음과 같이
📢
API Note: Optional is primarily intended for use as a method return type where there is a clear need to represent "no result," and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance. 메소드가 반환할 결과 값이 '없음'을 명백하게 표현할 필요가 있고, null을 반환하면 에러가 발생할 가능성이 높은 상황에서 메소드의 반환 타입으로 Optional을 사용하자는 것이 Optional을 만든 주된 목적이다. Optional 타입의 변수의 값은 절대 null이어서는 안 되며, 항상 Optional 인스턴스를 가리켜야 한다.
Java 8부터 추가된 null이 올 수 있는 값을 감싸는 Wrapper 클래스로,
NPE(NullPointerException)
Optional 변수에 절대로 null을 할당하지 말 것
// 나쁜 예
public Optional<Cart> fetchCart() {
Optional<Cart> emptyCart = null;
...
}
// 좋은 예
public Optional<Cart> fetchCart() {
Optional<Cart> emptyCart = Optional.empty();
...
}
Optional.get() 호출 전에 Optional 객체가 값을 가지고 있음을 확실히 할 것
// 나쁜 예
Optional<Cart> cart = ... ; // this is prone to be empty
...
// cart가 비어있으면 NoSuchElementException을 발생시킨다.
Cart myCart = cart.get();
// 좋은 예
if (cart.isPresent()) {
Cart myCart = cart.get();
... // do something with "myCart"
} else {
... // do something that doesn't call cart.get()
}
값이 없는 경우, Optional.orElse() 를 통해 이미 생성된 기본 값(객체)을 반환할 것
// 나쁜 예
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
if (status.isPresent()) {
return status.get();
} else {
return USER_STATUS;
}
}
// 좋은 예
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
return status.orElse(USER_STATUS);
}
값이 없는 경우, Optional.orElseGet()을 통해 이를 나타내는 객체를 제공할 것
// 나쁜 예
public String computeStatus() {
... // some code used to compute status
}
public String findUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
if (status.isPresent()) {
return status.get();
} else {
return computeStatus();
}
}
---
public String computeStatus() {
... // some code used to compute status
}
public String findUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
// status가 비어있지 않아도 computeStatus()가 호출됨
return status.orElse(computeStatus());
}
// 좋은 예
public String computeStatus() {
... // some code used to compute status
}
public String findUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
// status가 비어있는 경우만 computeStatus()가 호출됨
return status.orElseGet(this::computeStatus);
}
값이 없으면 orElseThorw(Supplier<? extends X> exceptionSupplier)를 통해 명시적 예외를 발생 시킬 것
// 나쁜 예
public String findUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
if (status.isPresent()) {
return status.get();
} else {
throw new IllegalStateException();
}
}
// 좋은 예
public String findUserStatus(long id) {
Optional<String> status = ... ; // prone to return an empty Optional
return status.orElseThrow(IllegalStateException::new);
}
선택 항목이 있고 Null 참조가 필요한 경우 orElse(null)를 사용할 것
// 나쁜 예
Method myMethod = ... ;
...
// contains an instance of MyClass or empty if "myMethod" is static
Optional<MyClass> instanceMyClass = ... ;
...
if (instanceMyClass.isPresent()) {
myMethod.invoke(instanceMyClass.get(), ...);
} else {
myMethod.invoke(null, ...);
}
// 좋은 예
Method myMethod = ... ;
...
// contains an instance of MyClass or empty if "myMethod" is static
Optional<MyClass> instanceMyClass = ... ;
...
myMethod.invoke(instanceMyClass.orElse(null), ...);
값이 있는 경우에 이를 사용하고 없는 경우에 아무 동작도 하지 않는다면, Optional.ifPresent() 를 활용할 것
// 나쁜 예
Optional<String> status = ... ;
...
if (status.isPresent()) {
System.out.println("Status: " + status.get());
}
// 좋은 예
Optional<String> status ... ;
...
status.ifPresent(System.out::println);
Lambdas의 is Present()-get() 쌍을 대체할 수 있는 옵션. 또는 Else/ 또는 ElseXXX
// 나쁜 예
List<Product> products = ... ;
Optional<Product> product = products.stream()
.filter(p -> p.getPrice() < price)
.findFirst();
if (product.isPresent()) {
return product.get().getName();
} else {
return "NOT FOUND";
}
---
List<Product> products = ... ;
Optional<Product> product = products.stream()
.filter(p -> p.getPrice() < price)
.findFirst();
return product.map(Product::getName)
.orElse("NOT FOUND");
---
Optional<Cart> cart = ... ;
Product product = ... ;
...
if(!cart.isPresent() ||
!cart.get().getItems().contains(product)) {
throw new NoSuchElementException();
}
// 좋은 예
List<Product> products = ... ;
return products.stream()
.filter(p -> p.getPrice() < price)
.findFirst()
.map(Product::getName)
.orElse("NOT FOUND");
---
Optional<Cart> cart = ... ;
Product product = ... ;
...
cart.filter(c -> c.getItems().contains(product)).orElseThrow();
단순 값을 가져오기 위해서 Optional을 사용하는 것을 피하라
// 나쁜 예
public String fetchStatus() {
String status = ... ;
return Optional.ofNullable(status).orElse("PENDING");
}
// 좋은 예
public String fetchStatus() {
String status = ... ;
return status == null ? "PENDING" : status;
}
Optional을 필드의 타입으로 사용하지 말 것
// 나쁜 예
public class Member {
private Optional<String> name;
}
// 좋은 예
public class Member {
private String name;
}
Optional을 생성자나 메소드 인자로 사용하지 말 것
// 나쁜 예
public class Customer {
private final String name; // cannot be null
private final Optional<String> postcode; // optional field, thus may be null
public Customer(String name, Optional<String> postcode) {
this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
this.postcode = postcode;
}
public Optional<String> getPostcode() {
return postcode;
}
...
}
// 좋은 예
public class Customer {
private final String name; // cannot be null
private final String postcode; // optional field, thus may be null
public Cart(String name, String postcode) {
this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
this.postcode = postcode;
}
public Optional<String> getPostcode() {
return Optional.ofNullable(postcode);
}
...
}