검사 예외는 발생한 문제를 프로그래머가 처리하여 안전성을 높이게 해주기 때문에
제대로 사용하면 API와 프로그램의 질을 높일 수 있다.
어떤 메서드가 검사 예외를 던진다면,
1. 해당 메서드를 호출하는 코드에서는 catch 블록을 두어 예외를 처리하거나 throws로 상위 호출자에게 던져야 한다.
2. 검사 예외를 던지는 메서드는 스트림 안에서 직접 사용할 수 없기(아이템 45~48) 때문에 부담이 된다.
프로그래머가 의미 있는 조치를 취할 수 없는 경우라면 비검사 예외를 사용하는 게 좋을 수도 있다.
따라서 검사 예외와 비검사 예외 중 어떤 걸 선택해야 할지는 프로그래머가 그 예외를 어떻게 다룰지 생각하면 알 수 있다.
} catch(TheCheckedException e){
e.printStackTrace();
System.exit(1);
}
만약, 더 나은 방법이 없다면 비검사 예외를 선택해야 한다.
검사 예외를 회피하는 방법으로 적절한 결과 타입을 담은 Optional을 반환하는 것이다.(아이템 55)
즉, 검사 예외를 던지는 대신 빈 옵셔널을 반환하는 것이다.
하지만, 예외가 발생한 이유를 알려주는 부가 정보를 담을 수 없다는 단점이 있다.
public class MemoryRepository {
private static final Map<Long, String> repo = new HashMap<>();
private static Long id = 0L;
public void save(Long id, String name) {
repo.put(++id, name);
}
public String findById(Long id) throws CustomCheckedException {
return repo.get(id);
}
}
이 코드를 사용하는 클라이언트 쪽에서는 항상 catch를 통해 예외를 잡아야 한다.
하지만, 매우 귀찮다...
try {
memoryRepository.findById(1L);
} catch (CustomCheckedException e) {
e.printStackTrace();
}
이때의 예외 처리 핵심은 NULL이 들어오는 것을 막는 것이라고 가정해보자.
public Optional<String> findById(Long id) {
return Optional.of(repo.get(id));
}
그러면 아래 처럼 해결할 수 있다.
즉, 문제가 발생하는 지점을 boolean으로 받아 true 일 경우에만 로직을 실행시킨다.
/**
* 검사 예외를 던지는 메서드
*/
try{
obj.action(args);
}catch(TheCheckedException e){
// 예외 상황에 대처한다.
}
/**
* 상태 검사 메서드와 비검사 예외를 던지는 메서드
*/
if(obj.actionPermitted(args)){
obj.action(args);
}else{
// 예외 상황에 대처한다.
}
이 처럼 리팩터링을 하면 더 쓰기 편한 API를 제공할 수 있다.
obj.actionPermitted(args)
는 상태 검사 메서드에 해당하므로, 외부 동기화 없이 여러 스레드가 동시에 접근할 수 잇거나 외부 요인에 의해 상태가 변경될 수 있다면 이 리팩터링은 적절하지 않다.
- 검사 예외는 프로그램의 안전성을 높여준다.
- API 호출자가 예외 복구를 할 수 없다면 비검사 예외를 던지자.
- API 호출자가 예외 복구를 할 수 있고, 해주길 바란다면 옵셔널을 반환할지 고민하자.
- 옵셔널로 처리하기에 충분한 정보를 제공하지 못 하면 검사 예외를 던지자