Skip to content

Latest commit

 

History

History
164 lines (107 loc) · 7.5 KB

옵셔널_반환은_신중히_하라.md

File metadata and controls

164 lines (107 loc) · 7.5 KB

이펙티브 자바 아이템 55 : 옵셔널 반환은 신중히 하라

비어있는 값을 반환해야 할 때

자바 8이전에는 값을 반환할 수 없을 때 두 가지 선택지가 있었다.

  1. 예외를 던진다.
  2. null을 반환한다.

두 가지 모두 문제가 있다.

→ 예외는 진짜 예외적인 상황에서만 사용해야한다. (아이템 69) 예외를 생성할 때 스택 추적 전체를 캡처하므로 비용도 생긴다.

→ null을 반환하게 된다면 별도로 null을 처리해야 하거나 처리를 무시하다가 언젠가 NPE가 발생 할 수 있다. null을 반환하게 한 실제 원인과는 전혀 상관 없는 코드에서 발생할 수 있다.

Optional의 등장

자바 8이 되면서 Optional<T>을 사용할 수 있게 되었다. 옵셔널은 T타입을 담고있거나, 아무것도 담지 않을 수 있다. 옵셔널은 원소를 최대 1개 가질 수 있는 불변 컬렉션이다. (실제 collection을 구현한 것은 아니다.)

보통 T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야할 때 T 대신 Optional<T>를 반환하도록 선언한다. 옵셔널은 예외를 던지는 메서드보다 유연하고 null을 반환하는 메서드보다 오류 가능성이 적다.

public static String findSomething(List<String> list) {
    if (list.isEmpty()) {
        return null;
    }

    for (String s : list) {
        if (s.equals("something")) {
            return s;
        }
    }
    return null;
}
public static Optional<String> findSomething(List<String> list) {
    if (list.isEmpty()) {
        return Optional.empty();
    }

    for (String s : list) {
        if (s.equals("something")) {
            return Optional.of(s);
        }
    }
    return Optional.empty();
}

Optional 안에 null을 담지 말자.

Optional.of() 메서드로 Optional 객체를 생성할 수 있다. 하지만 파라미터에 null을 담으면 NPE가 발생한다.

return Optional.of(null);

null을 담아야한다면 Optional.ofNullable() 메서드를 사용한다.

return Optional.ofNullable(null);

null을 포장하는 객체를 사용해서 다시 null을 담을 필요는 없다. Optional을 만든 취지를 무시하는 행동이다.

그럼 언제 사용해야 하나요?

책에서는 checkedExeption과 취지가 비슷하다고 설명한다. 반환값이 없을 수도 있음을 사용자에게 명확히 알려준다는 점에서만 비슷하다고 생각하면 좋을 것 같습니다.

결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional을 반환한다. 하지만 Optional도 엄연히 새로 할당하고 초기화해야 되는 객체이고 안에서 값을 꺼내려면 메서드를 호출하는 과정이 필요하다. 따라서 성능이 중요한 상황이라면 세심히 측정해보는 방법밖에 없다.

Optional을 처리하는 방법

기본값을 설정할 수 있다.

String find1 = something.orElse("NONE");

원하는 예외를 던질 수 있다.

String find2 = something.orElseThrow(IllegalArgumentException::new);

항상 값이 채워져 있다고 가정한다.

String find3 = something.get();

OrElse()에 설정하는 기본값은 Optional의 값이 있어도 생성된다.

Optional<String> something = findSomething(List.of("something"));

String find1 = something.**orElse**(nothing());

String find2 = something.**orElseGet**(() -> nothing());

static String nothing() {
    System.out.println("나 생성됐어");
    return "No";
}

스크린샷 2022-03-02 오후 11.02.34.png

orElse는 Optional의 값이 null이 아니더라도 생성되는 단점이 있다. 따라서 비용이 큰 값을 설정하게 된다면 부담이 될 수 있다. 그럴때 OrElseGet을 사용하면 초기 설정 비용을 낮출 수 있다.

특별한 쓰임에 대비한 메서드

Optional 클래스에서 filter, map, flatmap의 메서드도 사용할 수 있다. Stream API가 제공하는 메서드와 동일한 기능을 수행한다.

기본 메서드로 해결하기 어렵다면 위 메서드로 해결이 가능한지 검토해본다.

기본 메서드로 해결하기 어렵다면 위 메서드로 해결이 가능한지 검토해본다.

적합한 메서드를 찾지 못 했다면 isPresent() 메서드를 살펴보자. 값이 있다면 true, 없다면 false를 반환한다. 하지만 앞에 소개한 메서드로 대부분의 해결이 가능하므로 더 짧고 명확한 코드를 사용한다.

if (findSomething(List.of("something")).isPresent()) {
    System.out.println("찾았다.");
} else {
    System.out.println("못 찾았다.");
}

stream() 메서드

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

스크린샷 2022-03-03 오전 12.28.55.png

무조건 Optional로 감싼다고 이득은 아니다

컬렉션, 스트림, 배열, 옵셔널과 같은 컨테이너 타입은 옵셔널로 감싸면 안된다.

빈 리스트를 반환해야 한다면 Optional<List> 보다 이전에 배운 빈 List를 반환하는 게 좋다. 하나 더 예를 들어 Map의 키로 Optional을 사용하면 절대 안된다. 일단 맵 안에 키가 없다는 사실을 나타내는 방법이 두가지나 된다. 키가 없는 경우와, 키가 있지만 키가 속이 빈 옵셔널일 경우이다. 쓸데없이 복잡하게만 만들어 혼란과 오류를 야기한다.

List.of(Optional.of("A"), Optional.of("B"), Optional.empty());
Map.of(Optional.of("A"), 1, Optional.of("B"), 2);

박싱 타입을 Optional로 포장하지 말자

박싱된(Integer, Double, Long 같은) 타입을 담는 옵셔널은 값을 두번이나 감싸기 때문에 기본 타입보다 무거울 수 밖에 없다. 그래서 자바 API 설계자는 int, long, double 전용 옵셔널 클래스를 준비해놨다. OptionalInt, OptionalLong, OptionalDouble이다. 이 옵셔널들도 기본 옵셔널이 제공하는 메서드를 거의 다 제공한다. 모던 자바에 자세하게 나오는 내용

대체재가 있으니 박싱된 기본 타입을 담는 옵셔널을 반환하는 일은 없도록 하자.

Optional.of(Integer.valueOf(1));

옵셔널을 컬렉션의 키, 값, 원소, 배열의 원소로 사용하는 게 적절한 상황은 거어어어어의 없다.

그럼 필드로 써도 되나요?

옵셔널을 인스턴스 필드가 과연 적절한지 고민해봐야한다. 우선 옵셔널은 직렬화를 지원하지 않는다. 상황에 따라 값이 없을 수도 있는 필드가 많다면 사용할 수도 있지만 대부분 블로그에서 옵셔널을 인스턴스 필드로 사용하는 것을 권장하지 않는다.