-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Labels
🐎 08 Methods8장 메서드8장 메서드
Description
Chapter : 8. 메서드
Item : 55. 옵셔널 반환은 신중히 하라
Assignee : jseok0917
🍑 서론
메서드가 특정 조건에서 값을 반환할 수 없을 때
자바8 이전
- 예외를 던진다.
-
스택 추적 전체를 캡처하므로 비용이 비싸다
- 예외 발생 시 JVM은 스택 추적을 생성하여, 예외가 발생한 지점을 파악할 수 있도록 한다.
-
예외의 오용(item 69)
- 예외는 정말로 예외적인 상황에서 사용되야 한다.
- 프로그램의 흐름을 제어하기 위해 예외를 사용하는 것은 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만든다.
-
- Null을 반환한다.
- 메서드를 호출한 부분에서 별도의 null처리 코드를 작성해야 한다.
- 작성 안하면 런타임 시 nullPointerException 발생
- 메서드를 호출한 부분에서 별도의 null처리 코드를 작성해야 한다.
//예외발생 메서드
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 이후
- 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을 사용 이유
-
Optional은 NPE 발생을 방지하기 위해 사용
-
메서드가 반환할 결과값이 ‘없음’을 명백하게 표현할 필요가 있고, null을 반환하면 에러를 유발할 가능성이 높은 상황에서 메서드의 반환 타입으로 Optional을 사용하자는 것이 Optional을 만든 주된 목적
- 반환값이 없을 수도 있음을 API 사용자에게 명확히 알려줌
- optional을 볼 경우 클라이언트는 이에 대한 처리 코드를 작성할 것임
- 예시 중 하나가 orElse로 기본값을 설정해두는 것
//이거 예외처리 해줘야되나...?
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 반환 시 사용자가 취할 행동
- 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를 반환하는 것보다 빈 List를 반환하는게 좋음
-
int,long,double 전용 옵셔널 클래스(OptionalInt, OptionalLong, OptionalDouble) 가 있기 때문에 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자.
-
옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는게 적절한 상황은 거의 없음
- 복잡성을 키워 오류 가능성을 키움
-
애초에 옵셔널은 값이 없을 수도 있는 반환타입을 명시하기 위한 목적으로 개발됨
- 자바 언어 아키텍트인 브라이언 고츠는 Optional의 용도가 선택형 반환값을 지원하는 것이라고 명확하게 못박았음
- Optional 클래스는 필드 형식으로 사용할 것을 가정하지 않았으므로 Serializable 인터페이스를 구현하지 않기 때문에 도메인 모델에 Optional을 사용한다면 직렬화(serializable) 모델을 사용하는 도구나 프레임워크에서 문제가 생길 수 있다.
Optional을 사용하면 좋은 경우
- 결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야할 때 Optional를 반환
- 단, 옵셔널을 사용하면 새로운 객체를 할당하고 값을 꺼내기 위해 메서드를 호출하는 등으로 성능이 저하될 수 있다. → 성능이 중요할땐 안맞을 수 있음
- 박싱된 기본 타입을 담는 옵셔널은 값을 두 겹이나 감싸므로 기본타입 자체보다 무거울 수 밖에 없다.
- 이럴 땐 위에서 얘기했 듯, 전용 옵셔널 클래스(OptionalInt, OptionalLong, OptionalDouble)를 사용
- 박싱된 기본 타입을 담는 옵셔널은 값을 두 겹이나 감싸므로 기본타입 자체보다 무거울 수 밖에 없다.
🍑 결론
-
값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환 값이 없을 가능성을 염두에 둬야하는 메서드라면 옵셔널을 반환해야할 수 있다.
-
옵셔널 반환에는 성능 저하가 뒤따르므로 상황에 맞춰 사용하자.
-
옵셔널을 반환값 이외의 용도로 쓰는 경우는 매우 드물다.
- 옵셔널을 맵의 값으로 절대 사용하지 마라, 쓸데없이 복잡한 가능성만 높인다.
-
referenced by
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
🐎 08 Methods8장 메서드8장 메서드