개요

  • 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);
}

  • orElseorElseGet 차이는 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. 값 변환에 mapflatMap, 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가 구현되어 있다.
참고
chanhee.kim's profile image

chanhee.kim

2021-05-20 22:38

Read more posts by this author