peaches-book-study / effective-java

이펙티브 자바 3/E
0 stars 2 forks source link

Item 55. 옵셔널 반환은 신중히 하라 #56

Open jseok0917 opened 2 months ago

jseok0917 commented 2 months ago

Chapter : 8. 메서드

Item : 55. 옵셔널 반환은 신중히 하라

Assignee : jseok0917


🍑 서론

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

자바8 이전

  1. 예외를 던진다.

    • 스택 추적 전체를 캡처하므로 비용이 비싸다

      • 예외 발생 시 JVM은 스택 추적을 생성하여, 예외가 발생한 지점을 파악할 수 있도록 한다.
    • 예외의 오용(item 69)

      • 예외는 정말로 예외적인 상황에서 사용되야 한다.
      • 프로그램의 흐름을 제어하기 위해 예외를 사용하는 것은 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만든다.


  1. Null을 반환한다.
    • 메서드를 호출한 부분에서 별도의 null처리 코드를 작성해야 한다.
      • 작성 안하면 런타임 시 nullPointerException 발생
//예외발생 메서드
public Order[] getOrdersForUser_Exception(Long userId) {
    User user = userStore.findById(userId);
    if (user == null) {
        //사용자가 직접 정의해준 UserNotFoundException 발생
        //예외처리 비용이 비싸고, 프로그램의 흐름 제어를 위해 예외를 사용하는게 적절치 않을 수 있음
        throw new UserNotFoundException("User not found for id: " + userId);
    }

  //orderStore.findByUser()메서드는 
  //user객체를 인자로 주문목록 order[] 을 반환하는 메서드
    return orderStore.findByUser(user);
}

//Null반환 메서드
public Order[] getOrdersForUser_Null(Long userId) {
    User user = userStore.findById(userId);
    if (user == null) {
        return null; // 사용자가 존재하지 않으면 null 반환
        //이 메서드를 호출한 부분에서 null에 대한 처리 또 해줘야함(까먹을 수도 있다)
    }
    return orderStore.findByUser(user);
}



자바8 이후

  1. Optional 반환
    • Optional은 null이 아닌 T타입을 참조하거나 아무것도 담지 않을 수 있다.
    • Optional은 원소를 최대 1개 가질 수 있는 불변 컬렉션
    • 보통 T를 반환하지만, 특정 조건에서는 아무것도 반환하지 않아야할 때 T 대신 Optional을 반환
      • 유효한 반환값이 없을 때는 빈 결과를 반환
    • 옵셔널을 반환하는 메서드가 예외를 던지거나 null을 반환하는 것보다 오류 가능성이 적음
    • 옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자
      • 옵셔널을 도입한 취지를 완전히 무시하는 행위


🍑 본론

Optional의 사용

//구현은 어렵지않음, 그냥 타입을 E에서 Optional<E>로 바꿔주면 됨
//기본타입의 경우에는 전용 옵셔널 클래스 사용하면 됨
public Optional<Order[]> getOrdersForUser_Optional(Long userId) {
    User user = userStore.findById(userId);
    if (user == null) {
        return Optional.empty(); // 사용자가 존재하지 않으면 Optional.empty() 반환
    }

  //orderStore.findByUser()메서드는 
  //user객체를 인자로 주문목록 order[] 을 반환하는 메서드
    return Optional.of(orderStore.findByUser(user));
}

Optional을 사용 이유

//이거 예외처리 해줘야되나...?
User user1 = getOrdersForUser_Exception(1L); // 예외가 발생하는지 사용자는 알 수 없다.
User user2 = getOrdersForUser_Null(1L); // 사용자는 null 이 반환되는지 알 수 없다.
//옵셔널이니까 반드시 예외처리해줘야겠네!!
Optional<User> user3 = getOrdersForUser_Optional(1L); // 반환 값이 없을 수도 있음을 사용자는 알 수 있다.

        // 기존 방식: 직접 null 체크 후 처리
        if (user1 != null) {
            System.out.println(user1.toLowerCase());
        } else {
            System.out.println("비었어용^^");
        }

        // Optional을 사용한 방식
        if (user3.isPresent()) {
            System.out.println(user3.get().toLowerCase());
        } else {
            System.out.println("비었어용^^");
        }

        //방식2
        System.out.println(user3.orElse("비었어용^^"));
    }
}



Optional 반환 시 사용자가 취할 행동

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

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

//옵셔널 활용 3 - 항상 값이 채워져있다고 가정(get)
//잘못 판단한 것이라면 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
public static void main(String[] args) {
    // 예시로 사용할 Optional 변수
    Optional<String> optionalName = Optional.of("Stoney");

    // filter를 사용하여 값의 조건을 검사
    optionalName.filter(name -> name.startsWith("S"))
                .ifPresent(filteredName -> System.out.println("Filtered name: " + filteredName));

    // map을 사용하여 값을 추출하거나 변환
    String upperCaseName = optionalName.map(String::toUpperCase)
                                        .orElse("No name available");
    System.out.println("Name in uppercase: " + upperCaseName);

    // 값이 존재하지 않는 경우 map은 동작하지 않음
    Optional<String> emptyOptional = Optional.empty();
    String result = emptyOptional.map(String::toUpperCase)
                                    .orElse("비어있어용^^");
    System.out.println("Result: " + result);  // "비어있어용^^" 출력
}

//옵셔널 활용 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"));
    }
}



Optional을 사용하면 안되는 경우



Optional을 사용하면 좋은 경우

🍑 결론