본문 바로가기
Development/Spring

Java Optional 바르게 쓰기

by 메정 2021. 9. 23.
  1. isPresent()-get() 대신 orElse()/orElseGet()/orElseThrow()
  2. orElse(new ...) 대신 orElseGet(() -> new ...)
  3. 단지 값을 얻을 목적이라면 Optional 대신 null 비교
  4. Optional 대신 비어있는 컬렉션 반환
  5. Optional필드로 사용 금지
  6. Optional을 생성자나 메서드 인자로 사용 금지
  7. Optional을 컬렉션의 원소로 사용 금지
  8. of(), ofNullable() 혼동 주의
  9. int, long, double에 대해선OptionalInt, OptionalLong, OptionalDouble 사용하기

Optional가 만들어진 의도!

Brian Goetz라는 사람이 제작함

제작한 의도에 따른 주의사항 26가지는 공식 API 문서에 포함되어 있음

… it was not to be a general purpose Maybe type, as much as many people would have liked us to do so. Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result” …

Optional은 많은 사람들이 우리(자바 언어 설계자)에게 기대했던 범용적인 Maybe 타입과는 다르다. 라이브러리 메서드가 반환할 결과값이 ‘없음’을 명백하게 표현할 필요가 있는 곳에서 제한적으로 사용할 수 있는 메커니즘을 제공하는 것이 Optional을 만든 의도였다.

만들어진 주 목적

  1. 메서드가 반환할 결과값이 '없음'을 명백하게 표현할 필요가 있다.
  2. null을 반환하면 에러를 유발할 가능성이 높은 상황에 대해 메서드의 반환 타입을 Optional을 사용하는 것
    1. Optional 타입의 값은 절대 null이 되어서는 안되고, 항상 Optional 인스턴스를 가르켜야 함

isPresent()-get() 대신 orElse()/orElseGet()/orElseThrow()

  • isPresent() : 현재 Optional이 보유한 값이 null인지 확인
  • get() : null이 아니라면 get()을 통해 Optional의 보유 값을 가져올 수 있음
  • orElse(~) : Optional의 값이 있든 없든 무조건 실행
    • ~가 새객체를 생성하거나 새로운 연산을 수행하지 않고, 이미 생성되고 계산된 값인 경우에 사용해야 함
    • ~가 새로운 객체를 생성하거나 새로운 연산을 수행할 경우, orElseGet() 이용
  • orElseGet(*) : *는 Optional에 값이 없을 때만 전달된 람다식이 실행
  • orElseThrow(+) : +는 Optional에 값이 없을 때 예외 발생
// 안 좋음
Optional<Member> member = ...;
if (member.isPresent()) {
    return member.get();
} else {
    return null;
}

// 좋음
Optional<Member> member = ...;
return member.orElse(null);

// 안 좋음
Optional<Member> member = ...;
if (member.isPresent()) {
    return member.get();
} else {
    throw new NoSuchElementException();
}

// 좋음
Optional<Member> member = ...;
return member.orElseThrow(() -> new NoSuchElementException());

단순 값을 얻을 목적이라면?

Optional 보다는 null 비교가 좋음

// 안 좋음
return Optional.ofNullable(status).orElse(READY);
// 좋음
return status != null ? status : READY;

return 하고자 하는 값이 컬렉션인 경우,

Optional로 감싸 반환된 null 객체보다는 비어있는 컬렉션을 반환하는게 더 좋음

// 안 좋음
public interface MemberRepository<Member, Long> extends JpaRepository {
    Optional<List<Member>> findAllByNameContaining(String part);
}

// 안 좋음
List<Member> members = team.getMembers();
return Optional.ofNullable(members);

// 좋음
public interface MemberRepository<Member, Long> extends JpaRepository {
    List<Member> findAllByNameContaining(String part);  // null이 반환되지 않으므로 Optional 불필요
}

// 좋음
List<Member> members = team.getMembers();
return members != null ? members : Collections.emptyList();

Optional을 필드로 사용금지

Optional은 필드에 사용할 목적으로 만들어진 것이 아님!

따라서 필드로 사용하는 것은 좋지 않음

// 안 좋음
public class Member {

    private Long id;
    private String name;
    private Optional<String> email = Optional.empty();
}

// 좋음
public class Member {

    private Long id;
    private String name;
    private String email;
}

Optional을 생성자나 메서드 인자로 사용 금지

호출시마다 Optional을 생성해서 인자로 전달해줘야 함

호출되는 쪽, API나 라이브러리 메서드는 Optional이든, 아니든 null 체크를 하는게 안전하므로 굳이 Optional을 인자로 사용하지 말고, 별도로 null 체크하는 것이 바람직

// 안 좋음
public class HRManager {

    public void increaseSalary(Optional<Member> member) {
        member.ifPresent(member -> member.increaseSalary(10));
    }
}
hrManager.increaseSalary(Optional.ofNullable(member));

// 좋음
public class HRManager {

    public void increaseSalary(Member member) {
        if (member != null) {
            member.increaseSalary(10);
        }
    }
}
hrManager.increaseSalary(member);

Optional을 컬렉션의 원소로 사용 금지

컬렉션에는 많은 원소가 들어갈 수 있음

Optional을 원소로 사용하기 보다는 원소를 꺼낼 때마다 null 체크하는 것이 좋음

Map은 getOrDefault(), putIfAbsent(), computeIfAbsent(), computeIfPresent() 처럼 null 체크가 포함된 메서드를 제공

Map의 원소로 Optional을 사용하지 말고 Map이 제공하는 메서드를 활용하는 것이 좋음

// 안 좋음
Map<String, Optional<String>> sports = new HashMap<>();
sports.put("100", Optional.of("BasketBall"));
sports.put("101", Optional.ofNullable(someOtherSports));
String basketBall = sports.get("100").orElse("BasketBall");
String unknown = sports.get("101").orElse("");

// 좋음
Map<String, String> sports = new HashMap<>();
sports.put("100", "BasketBall");
sports.put("101", null);
String basketBall = sports.getOrDefault("100", "BasketBall");
String unknown = sports.computeIfAbsent("101", k -> "");

of()와 ofNullable() 주의

of()는 null이 아닐 때 사용 (null인 경우, NullPointException 발생)
ofNullable()은 null일 수도 있을 경우 사용

Optional 대신 OptionalInt, OptionalLong, OptionalDouble

Optional에 담기는 값이 int, long, double이라면 Boxing, UnBoxing이 발생할 수 있음

// 안 좋음
Optional<Integer> count = Optional.of(38);  // boxing 발생
for (int i = 0 ; i < count.get() ; i++) { ... }  // unboxing 발생

// 좋음
OptionalInt count = OptionalInt.of(38);  // boxing 발생 안 함
for (int i = 0 ; i < count.getAsInt() ; i++) { ... }  // unboxing 발생 안 함

Boxing : int(기본 자료형) → Integer(클래스)로 객체로 만드는 것

UnBoxing : Integer(클래스) → int(기본 자료형)로 바꾸는

참고

'Development > Spring' 카테고리의 다른 글

Spring Boot 로그 설정 (Logback)  (0) 2021.09.23
java.util.Optional T 클래스  (0) 2021.09.23
Unit Test에서 AssertThat을 사용  (0) 2021.09.23
테스트 코드 작성 시 유의사항  (0) 2021.09.23
@Validation 어노테이션  (0) 2021.09.23

댓글