자바 공부#4_02. Checked Exception, Unchecked Exception
자바에서는 실행 시 발생할 수 있는 오류(Runtime Error)를 크게 Exception과 Error 클래스로 구분한다.
그리고 이 중 Exception 클래스는 다음 두 가지로 다시 나눌 수 있다.
1. Checked Exception
2. Unchecked Exception (== RuntimeException 클래스와 자손들)
1. 에러 (Error)?
Error는 시스템에서 발생하는 치명적인 오류를 의미하며, 일반적인 애플리케이션에서는 복구가 어렵거나 불가능한 문제다.
ex)
- OutOfMemoryError: 메모리가 부족한 경우
- StackOverflowError: 재귀 호출로 인해 스택이 넘치는 경우
이러한 에러는 개발자가 직접 예외 처리하는 경우가 거의 없다.
대부분 시스템 자체의 문제이며, 애플리케이션 수준에서는 다루지 않는다.
2. 체크 예외 (Checked Exception)?
Checked Exception은 Exception 클래스의 하위 클래스이면서, RuntimeException의 하위 클래스가 아닌 예외들이다.
이들은 대부분 외부 요인에 의해 발생하고, 개발자가 사전에 예외 상황을 예상하고 처리해야 하는 경우에 해당한다.
컴파일 시점에서 예외 처리를 강제하며, try-catch 또는 throws를 통해 반드시 처리해야 한다.
ex)
- IOException, FileNotFoundException: 존재하지 않는 파일을 읽으려는 경우
- SQLException: 데이터베이스 접속 오류
3. 비체크 예외 (Unchecked Exception)?
Unchecked Exception은 RuntimeException 클래스 및 그 하위 클래스들로 구성되어 있다.
대부분 개발자의 실수나 코드 로직 문제에 의해 발생하며, 예외 처리가 강제되지 않는다.
ex)
- NullPointerException: null 객체의 메서드 호출
- IndexOutOfBoundsException: 잘못된 인덱스 접근
- IllegalArgumentException: 부적절한 인자 전달
코드상에서 예외가 발생할 수 있음을 인지하고 있다면, 적절히 처리해주는 것이 좋지만, 필수는 아니다.
Checked Exception vs Unchecked Exception
체크 예외와 비체크 예외의 가장 큰 차이는 예외에 대한 처리를 컴파일 시점에 강제하느냐 안 하느냐의 차이이다. 예외를 언제 던지느냐에 대한 차이가 아니다. 코드에 Checked Exception에 대한 예외 처리가 안 되어 있을 때에는 컴파일 에러가 발생한다.
예외 처리 방법
1. 예외 복구 (try-catch로 직접 처리)
예외가 발생하면 try-catch 블록 안에서 직접 예외를 처리하고 프로그램을 정상적으로 이어가는 방식이다.
public void readFile(String path) {
try {
FileReader reader = new FileReader(path);
// 파일 읽기 로직
} catch (FileNotFoundException e) {
System.out.println("파일이 존재하지 않습니다. 기본 파일로 대체합니다.");
// 기본 파일 읽기 시도 또는 사용자에게 알림
}
}
2. 예외처리 회피 (throws 키워드)
예외를 현재 메서드에서 처리하는 것이 아니라 호출한 쪽으로 떠넘기는 방식이다. 주로 Checked Exception에서 사용된다.
public void readFile(String path) throws FileNotFoundException {
FileReader reader = new FileReader(path);
// 파일 읽기 로직
}
3. 예외 전환 (다른 예외로 바꿔서 던지기)
하위 계층에서 발생한 예외를 상위 계층에서 더 의미 잇는 예외로 전환해서 던지는 방식이다. 주로 다음 두 가지 이유로 사용된다.
3-1. 여러 예외를 하나의 큰 예외로 묶기 위해
여러 종류의 상세 예외를 상위 계층에서 공통적으로 처리할 수 있도록 하나의 예외로 감싸서 던지는 방식이다.
try {
// 디스크 공간 부족 등의 예외 발생
throw new SpaceException("디스크 공간 부족");
} catch (SpaceException e) {
InstallException ie = new InstallException("설치 중 예외 발생");
ie.initCause(e); // 원인 예외 등록
throw ie;
}
initCause()를 사용하면 예외의 원인 예외(cause)를 저장할 수 있어, 상위 계층에서도 예외의 정확한 원인을 추적할 수 있다.
3-2. Checked Exception → Unchecked Exception 으로 바꾸기 위해
예외를 처리할 수 없는 상황인데 Checked Exception이 발생하는 경우, Unchecked Exception으로 바꿔서 던지면 호출하는 쪽에서 처리할지 말지 선택적으로 처리할 수 있게 된다.
try {
throw new MemoryException("메모리 부족");
} catch (MemoryException e) {
throw new RuntimeException(e); // Unchecked 예외로 변환
}
억지로 try-catch를 붙이거나 throws를 던지는 대신, 런타임 예외로 바꿔서 더 유연하게 예외를 다룰 수 있다.
이렇게 예외 전환을 사용하면, 예외 흐름을 더 명확하게 정리할 수 있고, 예외를 추상화하여 유지보수성과 가독성을 높일 수 있다.
Checked Exception이 기본적으로 Rollback되지 않는 이유 (Spring)
Spring의 기본 트랜잭션 처리 정책에 따르면, Error나 RuntimeException와 같은 Unchecked Exception이 발생했을 때에는 자동으로 트랜잭션이 rollback된다. 하지만 Checked Exception은 기본적으로 Rollback되지 않는다.
이는 Spring이 Checked Exception을 개발자가 복구 가능한 상황으로 판단할 가능성이 높다고 간주하기 때문이다. 예를 들어 특정 이미지 파일을 찾는 함수에서 파일이 없을 경우, 기본 이미지를 반환하는 방식으로 복구 전략을 세울 수 있다.
Spring은 이러한 예외 상황에서 트랜잭션을 무조건 rollback하는 대신, 개발자에게 rollback 여부를 명시적으로 지정하도록 한다.
따라서 Checked Exception의 경우에도 필요하다면 다음과 같이 설정하여 rollback을 명시할 수 있다.
@Transactional(rollbackFor = MyCheckedException.class)
public void doSomething() throws MyCheckedException {
// 중간에 checked 예외 발생 시 rollback
}
(다중 예외일 경우 다음과 같이 설정 가능)
@Transactional(rollbackFor = {IOException.class, SQLException.class})
Checked Exception, Unchecked Exception 어떤 걸 선택할까?
임의로 예외 클래스를 만들어 예외 처리하는 경우가 많은데, 이 때 try-catch로 묶어줄 필요가 있는 경우에만 Exception 클래스를 확장한다. 일반적으로 실행시 예외를 처리할 수 있는 경우에는 RuntimeException 클래스를 확장해 Unchecked Exception을 사용하는 것이 좋다.
참고:
- https://devlog-wjdrbs96.tistory.com/351
- https://hahahoho5915.tistory.com/67
-