예외의 본질
프로그램 실행 중 JVM이 수행 불가능한 연작을 감지하면 런타임 에러가 발생한다. Java에서는 이를 예외(Exception)라는 객체로 포장해 던진다. 예외는 정상적인 실행 흐름을 방해하는 비정상 상황을 표현하며, 적절히 대응하지 않으면 프로세스는 비정상 종료된다.
예외 계층 구조
모든 예외는 java.lang.Throwable을 거점으로 전개된다. 두 개의 직접 계열이 존재한다:
- Error: 메모리 부족, 스택 오버플로우 등 복구 불가능한 치명적 장애
- Exception: 프로그램 로직으로 수습 가능한 일반적 예외
예외의 분류
검사 예외(Checked Exception)
RuntimeException 계열을 제외한 모든 예외. 컴파일러가 처리 여부를 강제한다. 메서드 선언부에 throws로 명시하거나 try-catch로 감야 한다.
// 데이터베이스 연결 시 발생 가능
java.sql.SQLException
// 파일 입출력 시 발생 가능
java.io.IOException
비검사 예외(Unchecked Exception)
RuntimeException 및 그 하위 클래스. 컴파일 단계에서는 확인되지 않고 실행 시점에 노출된다. 주로 개발자의 실수나 잘못된 인자 전달로 인해 발생한다.
// 배열 인덱스 초과
int[] data = {1, 2, 3};
int value = data[5]; // ArrayIndexOutOfBoundsException
예외 처리 구문
기본 try-catch
try {
// 위험 코드
int quotient = divide(10, 0);
} catch (ArithmeticException ex) {
// 구체적인 예외 먼저 처리
System.err.println("0으로 나눌 수 없음: " + ex.getLocalizedMessage());
} catch (Exception ex) {
// 범용 예외는 후순위
System.err.println("알 수 없는 오류 발생");
}
finally 블록
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("config.txt"));
String line = reader.readLine();
processConfig(line);
} catch (IOException ex) {
log.error("설정 파일 읽기 실패", ex);
} finally {
// 리소스 해제는 반드시 수행
if (reader != null) {
try {
reader.close();
} catch (IOException ignored) {}
}
}
예외 정보 추출 메서드
| 메서드 | 반환 | 용도 |
|---|---|---|
getMessage() | String | 상세 메시지 문자열 |
toString() | String | 예외 유형과 메시지의 간략 조합 |
printStackTrace() | void | 콘솔에 스택 추적 출력 |
throw와 throws의 차이
// throws: 메서드 선언부에 예외 전파 선언
public void transferFunds(Account from, Account to, BigDecimal amt)
throws InsufficientBalanceException {
if (from.getBalance().compareTo(amt) < 0) {
// throw: 메서드 내부에서 예외 객체 직접 생성 및 발생
throw new InsufficientBalanceException("잔액 부족: " + from.getBalance());
}
// 이하 생략...
}
사용자 정의 예외
public class PaymentProcessingException extends Exception {
private final String transactionRef;
private final LocalDateTime failureTime;
// 기본 생성자
public PaymentProcessingException() {
this("결제 처리 중 오류", null, LocalDateTime.now());
}
// 메시지 포함 생성자
public PaymentProcessingException(String msg) {
this(msg, null, LocalDateTime.now());
}
// 완전 생성자
public PaymentProcessingException(String msg, String ref, LocalDateTime time) {
super(msg);
this.transactionRef = ref;
this.failureTime = time;
}
public String getTransactionRef() {
return transactionRef;
}
}
실무 적용 원칙
- 최소 범위 감시:
try블록은 예외가 발생할 가능성이 있는 코드만 감싼다 - 구체적 → 일반적 순서:
catch절은 하위 예외부터 상위 예외 순으로 배치 - 무의미한 전파 금지:
catch내부에서 단순히 다시 던지기만 하지 고, 의미 있는 처리나 로깅 수행 - 자원 해제 확보:
finally또는 try-with-resources로 외부 리소스 반납 보장 - 우아한 복구: 예외 발생 시에도 애플리케이션의 핵심 기능은 유지되도록 설계
try-with-resources 문법
Java 7부터 도입된 자동 자원 관리 메커니즘. AutoCloseable 구현체에 적용된다.
public String readFirstLine(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
// br.close()가 자동 호출됨, 예외 발생 여부와 무관
}