Optional 주의해서 사용하기

이민재·2024년 3월 10일
0

Optional

목록 보기
1/1

서론

이번에 Optional을 정리하면서 여러 자료를 찾아보던 중 Optional과 관련된 포스팅 하나가 정말 정리가 잘되었다고 생각하게 되었다.
https://dzone.com/articles/using-optional-correctly-is-not-optional
https://www.latera.kr/blog/2019-07-02-effective-optional/
[Java] Optional 올바르게 사용하기
블로그에 정리...를 빙자한 배껴쓰기를 하면서 내 머리에도 정리가 될 수 있도록 꼼꼼히 읽으면서 정리하려고 한다.


Optional 올바르게 사용하기

  1. Optional에 절대 null을 할당하면 안된다.
    나쁜 예 :
Optional<Member> findById(Long id) {
// find Member from db    
	if (result == 0) {
    return null;    	
    }
}

좋은 예 :

Optional<Member> findById(Long id) { 
	// find Member from db    
  	if (result == 0) {
      	return Optional.empty();    
  	}
}

당연히 Optional 대신 null 을 반환하는 것은 Optional 의 도입 의도와 맞지 않는다.
Optional 은 내부 값을 null 로 초기화한 싱글톤 객체를 Optional.empty() 메소드를 통해 제공하고 있다.
위에서 말한 "결과 없음"을 표현해야 하는 경우라면 null 대신 Optional.empty() 를 반환하자.

  1. Optional.get() 호출 전에 Optional 객체가 값을 가지고 있음을 확실히 할 것
    Optional.get() 메소드를 통해 해당 값을 접근 할 수 있다.
    만약 빈 Optional 객체에 get() 메소드를 호출한 경우 NoSuchElementException 이 발생하기 때문에 값을 가져오기 전에 반드시 값이 있는지 확인해야 한다.

  2. 값이 없는 경우, Optional.orElse() , Optional.orElseGet()을 통해 값을 제공한다.
    이전 글에서 정리했던 것 처럼 orElse는 Optional에 값이 있던 없던 무조건 실행된다.
    만약 값이 있다면 인자로 실행된 값은 무시되고 버려진다.

    • orElse는 이미 생성된었거나 계산된 값일 때만 사용한다.
    • 그렇지 않을 경우는 orElseGet()을 통해 불필요한 연산을 하지 않게 한다.
  3. 값이 없는 경우, Optional.orElseThrow() 를 통해 명시적으로 예외를 던질 것

    예시

    Member member = findById(1).orElseThrow(() -> new NoSuchElementException("Member Not Found"));
  4. isPresent() - get() 은 orElse() 나 orElseXXX 등으로 대체

     Optional<Member> optionalMember = findById(1);
     if(optionalMember.isPresent()) {
         System.out.println("member : " +optionalMember.get());
     } else {
         throw new MemberNotFoundException("Member Not Found id : " + 1);
     }

    좋은 예:

     Member member = findById(1)        
                 .orElseThrow(() -> new MemberNotFoundException("Member not found id : " + 1));
     System.out.println("member : " + member.get());
  5. Optional 을 생성자나 메소드 인자로 사용하지 말 것

    Optional 을 생성자나 메소드 인자로 사용하면, 호출할 때마다 Optional 을 생성해서 인자로 전달해줘야 한다.굳이 비싼 Optional 을 인자로 사용하지 말고 호출되는 쪽에 null 체크 책임을 남겨두는 것이 좋다.

        void increaseSalary (Member member,int salary){
            if (member != null) {
                member.increaseSalary(salary);
            }
        }
  1. 단지 값을 얻을 목적이라면 Optional 대신 null 비교

    Optional 은 비싸기 때문에 과도하게 사용하지 말아야 한다.단순히 값 또는 null 을 얻을 목적이라면 Optional 대신 null 비교를 사용하자

    return Optional.ofNullable(member).orElse(UNKNOWN);
    //변경
    return member != null ? member : UNKNOWN;
  2. Optional 을 빈 컬렉션이나 배열을 반환하는 데 사용하지 말 것

    컬렉션이나 배열로 복수의 결과를 반환하는 메소드가 "결과 없음"을 가장 명확하게 나타내는 방법은 대부분의 경우 빈(empty) 컬렉션 또는 배열을 반환하는 방법이다.

    List<Member> members = team.getMember();
    return Optional.ofNullable(members);
    
    //변경
    
    List<Member> members = team.getMembers();
    return members != null ? members : Collections.emptyList();

    JPA에서도 컬렉션을 Optional로 감싸는 것은 좋지 못하다.
    컬렉션을 반환하는 Spring Data JPA Repository 메소드는 null 을 반환하지 않고 비어있는 컬렉션을 반환해주므로 Optional 로 감싸서 반환 할 필요가 없다.

    public interface MemberRepository extends JpaRepository<Member, Long> {
    	Optional<List<Member>> findAllByNameContaining(String keyword);
    }
    
    //변경
    
    public interface MemberRepository extends JpaRepository<Member, Long> {
    	List<Member> findAllByNameContaining(String keyword);
    }
  3. Optional.of()Optional.ofNullable() 을 혼동하지 말 것

    of(X) 는 X 가 null 이 아님이 확실할 때만 사용해야 하며, X 가 null 이면 NullPointerException이 발생 한다.
    ofNullable(X) 은 X가 null 일 가능성이 있을 때 사용해야 하며, X 가 null 이 아님이 확실하면 of(X) 를 사용해야 한다.

    //나쁜 예 :
    return Optional.of(member.getName()); // member의 name이 null 이면 NPE 발생 
    return Optional.ofNullable(MEMBER_STATUS);
    //좋은 예 :
    return Optional.ofNullable(member.getName());
    return Optional.of(MEMBER_STATUS);
  4. 원시 타입의 Optional 에는 OptionalInt , OptionalLong , OptionalDouble 사용을 고려할 것

    원시 타입(primitive type)을 Optional 로 사용하면 Boxing 과 UnBoxing 을 거치면서 오버헤드가 생기게 된다.
    반드시 Optional 의 제네릭 타입에 맞춰야 하는 경우가 아니라면 int , long , double 타입에는 OptionalXXX 타입 사용을 고려하는 것이 좋다.
    이들은 내부 값을 래퍼 클래스가 아닌 원시 타입으로 갖고, 값의 존재 여부를 나타내는 isPresent 필드를 함께 갖는 구현체들이다.

    //나쁜 예 :
    Optional<Integer> cnt = Optional.of(10); // boxing 발생
    for(int i = 0; i < cnt.get(); i++) { ... } // unboxing 발생
    //좋은 예 :
    OptionalInt cnt = OptionalInt.of(10); // boxing 발생 안 함
    for(int i = 0; i < cnt.getAsInt(); i++) { ... } // unboxing 발생 안 함
    
  5. 내부 값 비교에는 Optional.equals 사용을 고려할 것

    기본적인 참조 확인과 타입 확인 이후에 두 Optional 의 동치성은 내부 값의 equals 구현이 결정한다.
    즉, Optional 객체 maybeA 와 maybeB 의 두 내부 객체 a 와 b 에 대해 a.equals(b) 가 true 이면maybeA.equals(maybeB) 도 true 이며 그 역도 성립한다.
    굳이 내부 값의 비교만을 위해 값을 꺼낼 필요는 없다는 의미이다.

    //나쁜 예 :
    boolean compareMemberById(long id1, long id2) {
        Optional<Member> maybeMemberA = findById(id1);
        Optional<Member> maybeMemberB = findById(id2);
        if(!maybeMemberA.isPresent() && !maybeMemberB.isPresent()) {return false; }    
        if (maybeMemberA.isPresent() && maybeMemberB.isPresent()) {
            return maybeMemberA.get().equals(maybeMemberB.get());    
        }    
        return false;
    }
    //좋은 예 :
    boolean compareMemberById(long id1, long id2) {
        Optional<Member> maybeMemberA = findById(id1);
        Optional<Member> maybeMemberB = findById(id2);
        if(!maybeMemberA.isPresent() && !maybeMemberB.isPresent()) { return false; }
        return findById(id1).equals(findById(id2));
    }
profile
초보 개발자

0개의 댓글