개요
Optional
은 반환값이 없음을 명백하게 필요한곳에서 제한적으로 사용하도록 만든건데.. 의도에 맞지 않게 쓰는 경우가 많고 오히려 더 복잡해지는 경우가 있어 써본다 아래는 API 공식문서에 나오는 API노트라는 형식에 나오는 문구이다.
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.
1. Optional
변수에 null 할당 금지
// 나쁜 예
public Optional<Cart> fetchCart() {
Optional<Cart> emptyCart = null;
}
// 좋은 예
public Optional<Cart> fetchCart(){
Optional<Cart> emptyCart = Optional.empty();
}
- 빈값으로 초기화 하려면
Optional.empty()
사용
2. Optional.get()
호출 전에 Optional
객체의 값을 확인 할 것
// 나쁜 예
Optional<Person> oPerson = findById(4);
String name = maybePerson.get().getName();
// 좋은 예
Optional<Person> oPerson = findById(4);
if (oPerson.ifPresent()) {
return oPerson.get();
}
return UNKNOWN_PERSON;
get()
했을때 값이 없으면 에러가 발생한다
3. 하지만 ifPresent()
방법보다 orElse(), orElseGet(), orElseThrow()
사용해라
// orElse
// 나쁜 예
public String findUserStatus(long id) {
Optional<String> status = Optional.of("활성화");
if (status.isPresent()) {
return status.get();
} else {
return "비활성화";
}
}
// 좋은 예
public String findUserStatus(long id) {
Optional<String> status = Optional.of("활성화");
return status.orElse("비활성화");
}
// orElseGet
// 나쁜 예
public String computeStatus() {
// status를 계산하여 리턴하는 로직
}
public String findUserStatus(long id) {
Optional<String> status = Optional.of("활성화");
if (status.isPresent()) {
return status.get();
} else {
return this.computeStatus();
}
}
// 좋은 예
public String findUserStatus(long id) {
Optional<String> status = Optional.of("활성화");
return status.orElseGet(this::computeStatus);
}
orElse
와orElseGet
차이는orElse
는 값이 있어도 호출되는 반면orElseGet
는 값이 없어야만 호출된다.
// orElseThrow()
// 나쁜 예
public String findUserStatus(long id) {
Optional<String> status = Optional.of("활성화");
if (status.isPresent()) {
return status.get();
} else {
throw new NoSuchElementException();
}
}
// 좋은 예
public String findUserStatus(long id) {
Optional<String> status = Optional.of("활성화");
return status.orElseThrow();
}
orElseThrow()
는 Java 10부터 사용가능하다. 이하 버전은orElseThrow(Supplier<? extends X> exceptionSupplier)
를 사용해야한다.
4. 값이 있는 경우 사용하고 없는 경우 아무 동작 하지않는다면 ifPresent()
를 활용할 것
// 나쁜 예
Optional<String> status = Optional.of("활성화");
if (status.isPresent()) {
System.out.println("Status: " + status.get());
}
// 좋은 예
Optional<String> status = Optional.of("활성화");
status.ifPresent(System.out::println);
5. Optional
을 필드의 타입으로 사용하지 말 것
// 나쁜 예
public class Customer {
Optioanl<String> zip = Optional.empty();
}
// 좋은 예
public class Customer {
String zip;
String zip = "";
}
Serializable
가 구현 안되어 있고Optional
은 반환 타입을 위해 설계된 타입이므로 안티패턴이다.- 추가로 생성자, 메서드, Setter 인자로 사용하지말아야 된다.
6. Optional
을 컬렉션과 같이 사용하지 말 것
// 나쁜 예
public Optional<List<String>> fetchCartItems() {
List<String> items = cart.getItems();
return Optional.ofNullable(items);
}
// 좋은 예
public List<String> fetchCartItems() {
List<String> items = cart.getItems();
return items == null ? Collections.emptyList() : items;
}
- 오히려 비즈니스로직에 더 복잡해진다.
7. 기본 자료형에 Optional
말고 OptionalInt
,OptionalLong
,OptionalDouble
을 사용해라
// 나쁜 예
Optional<Integer> price = Optional.of(50);
Optional<Long> price = Optional.of(50L);
Optional<Double> price = Optional.of(50.43d);
// 좋은 예
OptionalInt price = OptionalInt.of(50);
OptionalLong price = OptionalLong.of(50L);
OptionalDouble price = OptionalDouble.of(50.43d);
8. 값 비교할때 Optional.equals
를 그대로 사용할 것
Optional<String> actual = Optional.of("shoes");
Optional<String> expected = Optional.of("shoes");
// 나쁜 예
expected.get().equals(actual.get());
// 좋은 예
expected.equals(actual);
equals
가 구현 되어있으므로 구지 get()을 해서 비교할 필요가 없다.
9. 값 변환에 map
과 flatMap
, filter
사용을 고려할 것
Optional<String> lowerName = Optional.of("optional");
// 나쁜 예
if(lowerName.isPresent()) {
upperName = Optional.of(lowerName.get().toUpperCase());
} else {
upperName = Optional.empty();
}
// 좋은 예
Optional<String> upperName = lowerName.map(String::toUpperCase);
String uppercase = items.stream()
.filter(i -> i.getPrice() > 50)
.findFirst()
.flatMap(i -> Optional.of(i.getName()))
.map(String::toUpperCase).orElse("NOT FOUND");
- 스트림 기반의 API가 구현되어 있다.