NMP-Study / EffectiveJava2018

Effective Java Study
9 stars 0 forks source link

아이템 55. 옵셔널 반환은 신중히 하라 #55

Closed madplay closed 5 years ago

seryang commented 5 years ago

메서드가 특정 조건에서 값을 반환할 수 없을 때

예를 들면..

school.getClassRoom().getTeacher().getSubject().getSubjectName();
if(school != null) {
    ClassRoom classRoom = school.getClassRoom();
    if(classRoom != null) {
        Teacher teacher = classRoom.getTeacher();
        if(teacher != null) {
            Subject subject = teacher.getSubject();
            if(subject != null) {
                String subjectName = subject.getSubjectName();
                return subjectName;
            }
        }
    }
}
return null;

Optional이란?

55-1. 컬렉션에서 최댓값을 구한다(컬렉션이 비었으면 예외를 던진다)

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

    public static void main(String[] args) {
        List<String> words = Arrays.asList(args);
        System.out.println(max(words));
    }

결과

Exception in thread "main" java.lang.IllegalArgumentException: 빈 컬렉션
    at item55.Main.max(Main.java:10)
    at item55.Main.main(Main.java:23)

55-2. 컬렉션에서 최댓값을 구해 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 void main(String[] args) {
        List<String> words = Arrays.asList(args);
        System.out.println(max(words));
    }

결과

Optional.empty

55-3. max를 스트림 버전으로 다시 작성

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

맨 처음 Sample 코드를 Optional로 수정한다면 아래와 같음

// Before
if(school != null) {
    ClassRoom classRoom = school.getClassRoom();
    if(classRoom != null) {
        Teacher teacher = classRoom.getTeacher();
        if(teacher != null) {
            Subject subject = teacher.getSubject();
            if(subject != null) {
                String subjectName = subject.getSubjectName();
                return subjectName;
            }
        }
    }
}
return null;

// After
Optional.ofNullable(school).map(School::getClassRoom)    //Optional<School>
                           .map(ClassRoom::getTeacher)   //Optional<ClassRoom>
                           .map(Teacher::getSubject)     //Optional<Teacher>
                           .map(Subject::getSubjectName) //Optional<Subject>
                           .orElse(null);

null을 반환하거나 예외를 던지는 대신 옵셔널 반환을 선택해야 하는 기준

옵셔널은 검사 예외와 취지가 비슷하다 #71

  • 반환값이 없을 수도 있음을 API 사용자에게 명확히 알려준다.
  • 비검사 예외를 던지거나 null을 반환한다면 API 사용자는 그 사실을 인지하지 못함 (런타임에서 예상치 못한 장애)
  • 검사 예외(checked Exception)를 던지면 클라이언트에서는 반드시 이에 대처하는 코드(try-catch 예외 처리)를 작성해넣음
  1. 원하는 예외를 던질 수 있다.

    Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
    • 값이 없는 경우 원하는 예외를 던질 수 있다.
  2. 항상 값이 채워져 있는 경우

    Element lastNobleGas = max(Elements.NOBLE_GASES).get();
    • 옵셔널에 항상 값이 채워져 있다고 확신할때 사용
    • 잘못 판단한 것이라면 NoSuchElementException 발생
  3. 기본값을 설정하는 비용이 큰 경우

    Connection connection = getConnection(datasource).orElseGet(() -> getLocalConnection());
    • 기본값을 설정하는 비용이 아주 커서 부담이 되는 경우 (Supplier<T>를 인수로 받는) orElseGet을 사용하면, 값이 처음 필요할 때 Supplier를 사용해 생성하므로 초기 생성비용을 낮출 수 있음

[참고] Optional 메서드 정리

Optional 안티 패턴

  1. 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸지 말것

    • Optional<List<T>>를 반환하기 보다는 빈 List를 반환하는 것이 좋음 #54
    • 빈 컨테이너를 그대로 반환하면 클라이언트에 옵셔널 처리 코드를 넣지 않아도 됨
  2. Optional을 Map의 키나 값으로 사용하지 말자

    • 만약 Optional을 Map에서 사용한다면 모호한 상황이 발생함
      • Key 자체가 없는 경우
      • Key는 있지만, 속이 빈 Optional인 경우
      • 쓸데없이 복잡도만 높아지게 되고 전혀 쓸모없는 짓이니 사용하지 말자.
    • 일반화 하자면, Optional은 Collection의 키, 값, 원소나 배열의 원소로 사용하는게 적절한 상황은 거의 없음

정리