티스토리 뷰

반응형

자바8 이전

자바8 이전에는 메서드가 특정 조건에서 값을 반환할 수 없을 때 두가지 선택지가 있었다.

  • 예외 던지기
    • 예외는 진짜 예외적인 상황에서만 사용해야함
    • 예외를 생성할 때 스택 추적 전체를 캡쳐하므로 비용이 큼
  • 반환 타입이 객체 참조라면 null 반환
    • 클라이언트에서 null을 반환하는 메서드를 호출할 때 추가적으로 null 처리 코드를 작성해야함
//컬렉션에서 최대값을 구함 - 컬렉션이 비었으면 예외 던지기
public static <E extends Comparable<E>> E max(Collection<E> c) {
    if (c.isEmpty())
        throw new IllegalArgumentException("Empty collection");

    E result = null;
    for (E e : c)
        if (result == null || e.compareTo(result) > 0)
            result = Objects.requireNonNull(e);

    return result;
}

 

자바8 이후

  • 자바8부터는 Optional가 생기면서 null이 아닌 T타입을 참조하거나 아무것도 담지 않을 수 있게됨.
  • 옵셔널은 원소를 최대 1개 가질 수 있는 불변 컬렉션
  • 보통은 T를 반환하지만, 특정 조건에서는 아무것도 반환하지 않아야 할 때 T 대신 Optional를 반환 → 유효한 반환값이 없을 때는 빈 결과를 반환
  • 옵셔널을 반환하는 메서드가 예외를 던지거나 null을 반환하는 것보다 오류 가능성이 적음
  • 옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자.
//컬렉션에서 최댓값 구하기 - Optional<E> 반환
//정적 팩터리를 사용해 옵셔널을 생성 
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);
}
//스트림 버전 (Optional<E> 반환)
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
    return c.stream().max(Comparator.naturalOrder());
}

 

 

Optional을 반환했을 때 장점

옵셔널은 검사 예외와 취지가 비슷함 → 반환 값이 없을 수도 있음을 API 사용자에게 명확히 알려줌. 검사 예외를 던진다면 클라이언트는 이에 대한 처리 코드를 작성할 것임

//옵셔널 활용 1 - 기본 값 정해두기
String lastWordInLexicon = max(words).orElse("단어 없음...");

//옵셔널 활용 2 - 원하는 예외 던지기
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);

//옵셔널 활용 3 - 항상 값이 채워져있다고 가정 
//잘못 판단한 것이라면 NoSuchElementException 발생
Element lastNobleGas = max(Elements.NOBLE_GASES).get();

//옵셔널 활용 4 - 기본값 설정 비용이 클 경우
//Supplier<T>를 인수로 받는 orElseGet
public static String orElseGetBenchmark() {
    return Optional.of("fruit").orElseGet(() -> getRandomName());
}

//옵셔널 활용 5 - filter, map, flatMap, ifPresent

//옵셔널 활용 6 - isPresent 메서드
//옵셔널이 채워져있으면 true, 비어있으면 false 반환
//isPresent를 사용한 코드는 대게 앞에 언급한 메서드들로 대체 가능 -> 그게 더 간결하고 명확함
public class ParentPid {
    public static void main(String[] args) {
        ProcessHandle ph = ProcessHandle.current();

        // isPresent 메서드를 사용한 코드
        Optional<ProcessHandle> parentProcess = ph.parent();
        System.out.println("Parent PID: " + (parentProcess.isPresent() ?
                String.valueOf(parentProcess.get().pid()) : "N/A"));

        // 위 코드를 map 메서드로 대체 다듬은 코드 -> 더 간결,명확
        System.out.println("Parent PID: " +
            ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));
    }
}

마지막 옵셔널 활용 6에서의 코드를 스트림을 사용할 경우에 다음과 같다.

//옵셔널에 값이 있다면 꺼내서 매핑
streamOfOptionals
    .filter(Optional::isPresent)
    .map(Optional::get);

//자바9부터는 Optional을 Stream으로 변환해주는 Optional.stream() 추가
//옵셔널에 값이 있으면 그 값을 원소로 담은 스트림으로, 없으면 빈 스트림으로 변환
//flatMap 메서드와 조합하여 더 명료한 코드로 수정 가능
streamOfOptionals.flatMap(Optional::stream)

 

옵셔널을 사용하면 안되는 경우

  • 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안됨. 빈 Optional<List>를 반환하는 것보다 빈 List를 반환하는게 좋음
  • int,long,double 전용 옵셔널 클래스(OptionalInt, OptionalLong, OptionalDouble) 가 있기 때문에 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자.
  • 옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는게 적절한 상황은 거의 없음 (복잡성을 키워 오류 가능성을 키움)

 

옵셔널을 사용하면 좋은 경우

결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야할 때 Optional를 반환

단, 옵셔널을 사용하면 새로운 객체를 할당하고 값을 꺼내기 위해 메서드를 호출하는 등으로 성능이 저하될 수 있다. → 성능이 중요할땐 안맞을 수 있음

 

 

결론

값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환 값이 없을 가능성을 염두에 둬야하는 메서드라면 옵셔널을 반환해야할 수 있다.

옵셔널 반환에는 성능 저하가 뒤따르므로 상황에 맞춰 사용하자.

옵셔널을 반환값 이외의 용도로 쓰는 경우는 매우 드물다.

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday