베어_
TechBear
베어_
전체 방문자
오늘
어제
  • 분류 전체보기 (336)
    • Spring (33)
      • 개념 (13)
      • Security (5)
      • 실습 (1)
      • 토비 스프링 (11)
    • JPA (6)
    • 프로젝트 기록 (24)
    • DB (13)
    • JAVA (18)
    • 알고리즘 (50)
      • 유형정리 (8)
      • Baekjoon (21)
      • LeetCode (18)
    • 디자인패턴 (0)
    • 개발서적 (79)
      • Effective Java (78)
      • 객체지향의 사실과 오해 (1)
    • 독후감 (4)
    • 보안 (2)
    • 운영체제(OS) (53)
      • 공룡책 (53)
    • 컴퓨터 네트워크 (28)
      • 컴퓨터 네트워크 하향식 접근 (23)
    • 자료구조 (1)
    • DevOps (2)
    • 앱 개발 (20)
      • 안드로이드 스튜디오 (20)

블로그 메뉴

    공지사항

    인기 글

    태그

    • 백준
    • BFS
    • Spring
    • dfs
    • jpa
    • leetcode
    • 자바8
    • 알고리즘
    • java
    • 코드업
    • 이펙티브자바
    • 스프링
    • 함수형인터페이스
    • C++
    • 스레드
    • 데이터베이스
    • 자바
    • 운영체제
    • 스프링시큐리티
    • 토비스프링

    최근 댓글

    최근 글

    티스토리

    hELLO · Designed By 정상우.
    베어_

    TechBear

    개발서적/Effective Java

    [Effective Java] 옵셔널 반환은 신중히 하라

    2023. 6. 2. 05:45

    메서드가 특정 조건에서 값을 반환할 수 없을 때 취하는 2가지 방법 (자바 8이전)

    1. 예외를 던진다.

    예외는 진짜 예외적인 상황에서만 사용해야 하며 예외를 생성할 때 스택 추적 전체를 캡쳐하므로 비용이 만만치 않다.

    2. null을 반환한다.

    null을 반환할 수 있는 메서드를 호출할 때는 별도의 nmull 처리 코드를 추가해야 한다.

    Optional의 등장

    자바 8 이후로 null이 아닌 T타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다. 옵셔널은 원소를 최대 1개 가질 수 있는 '불변' 컬렉션이다. 옵셔널을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉬우며, null을 반환하는 메서드보다 오류 가능성이 작다.

    옵셔널을 반환하도록 구현하기 위해서는 적절한 정적 팩터리를 사용해 옵셔널을 생성해주기만 하면 된다.

    public class Max {
        // 코드 55-1 컬렉션에서 최댓값을 구한다. - 컬렉션이 비었으면 예외를 던진다. (327쪽)
        public static <E extends Comparable<E>> E max(Collection<E> c) {
            if (c.isEmpty())
                throw new IllegalArgumentException("빈 컬렉션");
    
            E result = null;
            for (E e : c)
                if (result == null || e.compareTo(result) > 0)
                    result = Objects.requireNonNull(e);
    
            return result;
        }
    }

    빈 옵셔널은 Optional.empty()로 만들고, 값이 든 옵셔널은 Optional.of(value)로 생성한다. Optional.of(value) 메서드에 null을 넣으면 예외가 발생하니 주의해야 하며, null 값도 허용하는 옵셔널을 만들려면 Optional.ofNullable(value)를 사용하자. 또한 옵셔널을 반환하는 메서드에서는 절대로 null을 반환하지 말자. Optional 등장 취지에 맞지 않는 행위이다.

    public static <E extends Comparable<E>>
    Optional<E> max(Collection<E> c) {
        if (c.isEmpty())
            return Optional.empty();
    
        E result = null;
        for (E e : c)
            if (result == null || e.compareTo(result) > 0)
                result = Objects.requireNonNull(e);
    
        return Optional.of(result);
    }

    스트림의 종단 연산 중 상당수가 옵셔널을 반환한다. 앞의 코드를 스트림을 이용해서 바꾸면 다음과 같다.

    public static <E extends Comparable<E>>
    Optional<E> max(Collection<E> c) {
        return c.stream().max(Comparator.naturalOrder());
    }

    옵셔널 반환의 선택 기준

    옵셔널은 검사 예외와 취지가 비슷하다. 즉, 반환값이 없을 수도 있음을 API 사용자에게 명확히 알려준다. 메서드가 옵셔널을 반환한다면 클라이언트는 값을 받지 못했을 때 취할 행동을 선택해야 한다.

    1. 기본값을 설정하는 방법

    String lastWordInLexicon = max(words).orElse("단어 없음...");

     

    2. 상황에 맞는 예외를 던진다.

    실제 예외가 아니라 예외 팩터리를 건넴으로써 예외가 실제로 발생하지 않는한 예외 생성 비용은 들지 않는다.

    Toy myToy = max(Toys).orElseThrow(TemperTantrumException::new);

     

    3. orElseGet

    기본값을 설정하는 비용이 커서 부담이 될 때는 Supplier를 인수로 받는 orElseGet을 사용하면, 값이 처음 필요할 때 Supplier를 사용해 생성하므로 초기 설정 비용을 낮출 수 있다.

    4. fliter, map, flatMap, ifPresent 등을 이용한다.

     

    5. isPresent 메서드를 사용한다.

    isPresent를 쓴 코드 중 상당수는 앞서 언급한 메서드로 대체할 수 있으면 그렇게 하면 더 짧고 명확하고 용법에 맞는 코드가 되기 때문에 isPresent사용은 마지막 방법이 되어야 한다.

    public class ParentPid {
        public static void main(String[] args) {
            ProcessHandle ph = ProcessHandle.current();
    
            // isPresent를 적절치 못하게 사용했다.
            Optional<ProcessHandle> parentProcess = ph.parent();
            System.out.println("부모 PID: " + (parentProcess.isPresent() ?
                    String.valueOf(parentProcess.get().pid()) : "N/A"));
    
            // 같은 기능을 Optional의 map를 이용해 개선한 코드
            System.out.println("부모 PID: " +
                ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));
        }
    }

    스트림을 사용한다면 옵셔널들을 Stream<Optional>로 받아서, 그 중 채워진 옵셔널들에서 값을 뽑아 Stream에 건네 담아 처리하는 경우가 많다.

    streamOfOptionals
            .filter(Optional::isPresent)
            .map(Optional::get)

    자바 9에서부터는 Optional에 stream() 메서드가 추가되었다. 이 메서드는 Optional을 Stream으로 변환해주는 어댑터다. 옵셔널에 값이 있으면 그 값을 원소로 담은 스트림으로, 값이 없다면 빈 스트림으로 변환한다.

    streamOfOptionals
            .flatMap(Optional::stream)

    옵셔널이 독이 되는 경우

    1. 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안 된다.
      -> 빈 컬렉션으로 반환하는 게 클라이언트의 코드를 더럽히지 않는다.
    2. 성능이 중요한 경우 Optional 사용은 재고려해보자.
    3. 박싱된 기본 타입을 담는 옵셔널은 사용하지 말자
      -> 값을 두 겹이나 감싸기 때문에 더 무겁다
      -> OptionalInt, OptionalLong, OptionalDouble과 같은 전용 옵셔널 클래스들을 이용하자.
    4. 옵셔널을 맵의 값으로 사용하면 안된다.
      -> 맵 안에 키가 없다는 사실을 나타내는 방법이 두 가지가 되며, 쓸데 없이 복잡해진다.
    저작자표시 비영리 변경금지 (새창열림)
      '개발서적/Effective Java' 카테고리의 다른 글
      • [Effective Java] 지역변수의 범위를 최소화하라
      • [Effective Java] 공개된 API 요소에는 항상 문서화 주석을 작성하라
      • [Effective Java] null이 아닌, 빈 컬렉션이나 배열을 반환하라
      • [Effective Java] 가변인수를 신중히 사용하라
      베어_
      베어_
      Today I learned | 문제를 해결하는 개발자

      티스토리툴바