[JAVA] 자바 예외(Exception) 구분 - Checked Exception, Unchecked Exception
예외(Exception) 란?
예외(Exception)란 입력 값에 대한 처리가 불가능하거나, 프로그램 실행 중에 참조된 값이 잘못된 경우 등 정상적인 프로그램의 흐름을 어긋나는 것을 말한다. 그리고 자바에서 예외는 개발자가 직접 처리할 수 있기 때문에 예외 상황을 미리 예측하여 핸들링할 수 있다. 이러한 예로는 RuntimeException, SQLException, IOException 등이 있다.
한편, 에러(Error)는 시스템에 무엇인가 비정상적인 상황이 발생한 경우에 사용된다. 주로 자바 가상머신에서 발생시키는 것이며 예외와 반대로 이를 애플리케이션 코드에서 잡으려고 하면 안 된다. (사실 잡아도 방법이 없다.) 에러의 예로는 OutOfMemoryError, ThreadDeath, StackOverflowError 등이 있다.
try {
// ...
} catch(StackOverflowError e) {
// ... Error에 대한 처리는 개발자가 미리 예측할 수 없기 때문에 대부분 사용 X
} catch(RuntimeException e) {
// ...
} catch(Exception e) {
// ...
}
예외 구분
Exception은 Checked Exception과 Unchecked Exception으로 구분할 수 있는데, 간단하게 RuntimeException을 상속하지 않는 클래스는 Checked Exception, 반대로 상속한 클래스는 Unchecked Exception으로 분류할 수 있다.
또한, Checked Exception은 프로그래머의 실수에 의해서 발생할 수 있는 예외, Unchecked Exception은 외부의 영향으로 발생할 수 있는 예외라고 할 수 있다.
예외 처리
예외를 처리하는 방법에는 예외 복구, 예외 처리 회피, 예외 전환 방법이 있다.
예외 복구
예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 방법으로, 정해진 시간/횟수만큼 재시도를 반복한다.
최대 재시도 횟수를 초과하는 경우 예외를 발생시킨다.
final int RETRY_CNT = 10;
public void someMethod() {
int retryCnt = 0;
while(retryCnt > RETRY_CNT) {
try {
// ...
} catch(Exception e) {
// 로그 출력, 재시도 진행
} finally {
// 리소스 반납 및 정리 작업
}
}
// 재시도 횟수 초과 시 예외처리 직접 발생
throw new RuntimeException("재시도 처리 실패...");
}
예외처리 회피
예외 처리를 직접 담당하지 않고 호출한 쪽으로 던져 회피하는 방법이다. 필요 시 내부에서 어느 정도 예외 처리를 하고 던지는 것이 좋다. 긴밀하게 역할을 분담하고 있는 관계가 아니라면 예외를 그냥 던지는 것은 무책임하다.
// 예시 1
public void someMethod1() throws SQLException {
// ...
}
// 예시 2
public void someMethod2() throws SQLException, IOException {
try {
// ...
} catch(SQLException e) {
// ...
throw e;
} catch(IOException e) {
// ...
throw e;
}
}
예외 전환
예외를 밖으로 던지지만, 예외처리 회피와는 달리 조금 더 명확한 의미로 전달하기 위해 적합한 의미를 가진 예외로 전환해 넘기는 방법이다. 예외 처리를 단순하게 만들기 위해 포장(wrap) 할 수도 있다.
// 예시 1 (예외 전환)
public void someMethod1() throws SQLException, IOException {
try {
// ...
} catch(SQLException e) {
// ErrorCode가 MySQL의 "Duplicate Entry(1062)"이면 예외 전환
if (e.getERrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY)
throw DuplicateUserException();
else
throw e;
}
}
// 예시 2 (예외 포장)
public void someMethod2() throws SQLException, IOException {
try {
// ...
} catch (NamingException ne) {
throw new EJBException(ne);
} catch (SQLException se) {
throw new EJBException(se);
} catch (RemoteException re) {
throw new EJBException(re);
}
}
예외 전달을 할 때, 예외를 중첩 예외(nested exception)로 만들면 좋다. 중첩 예외는 getCause() 메소드를 이용하여 처음 발생한 예외가 무엇인지 확인할 수 있게 해준다.
catch(SQLException e) {
// ...
throw DuplicateUserIdException(e);
}
catch(SQLException e) {
...
throw DuplicateUserIdException().initCause(e);
}
initCause()를 이용해 근본 원인이 되는 예외를 넣어주어도 된다.
정리하면
자바에서 예외는 RuntimeException을 상속하지 않고 꼭 처리해야 하는 Checked Exception과 반대로 명시적으로 처리하지 않아도 되는 Unchecked Exception로 기본적으로 구분할 수 있다.
Checked Exception은 프로그래머의 실수으로 발생할 수 있는 예외이기 때문에 반드시 예외 처리를 해야한다.
Unchecked Exception은 외부의 영향에 의해서 발생할 수 있는 예외로 필요 시 예외 처리를 해야한다.