JavaMedium#58
Java의 Checked Exception과 Unchecked Exception의 차이점을 설명해주세요.
#Java#예외처리#Exception
힌트
컴파일 시점 강제 여부와 Exception 클래스 계층을 생각해보세요.
정답 및 해설
Java의 Checked Exception과 Unchecked Exception의 차이점을 설명해주세요.
Java의 예외(Exception) 체계는 크게 Checked Exception, Unchecked Exception, Error로 구분됩니다. 이 구분을 이해하면 예외를 언제 처리하고 언제 전파할지 올바른 설계 결정을 내릴 수 있습니다.
Java 예외 계층 구조
Throwable
├── Error (JVM 수준 심각한 오류)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── ...
└── Exception
├── IOException ← Checked Exception
├── SQLException ← Checked Exception
├── ClassNotFoundException ← Checked Exception
└── RuntimeException ← Unchecked Exception
├── NullPointerException
├── ArrayIndexOutOfBoundsException
├── IllegalArgumentException
├── ClassCastException
└── ...
Checked Exception
Exception을 상속하지만 RuntimeException을 상속하지 않는 예외입니다. 컴파일러가 처리를 강제합니다.
특징
- 컴파일러가
try-catch또는throws선언을 강제 - 주로 회복 가능한 외부 오류를 나타냄 (네트워크, 파일 I/O, DB 등)
- 메서드 시그니처에
throws로 명시해야 함
// throws 선언 방식 — 호출자에게 처리 위임
public void readFile(String path) throws IOException {
FileReader reader = new FileReader(path); // IOException 발생 가능
// ...
}
// try-catch 방식 — 직접 처리
public void readFileWithHandling(String path) {
try {
FileReader reader = new FileReader(path);
BufferedReader br = new BufferedReader(reader);
String line = br.readLine(); // IOException
br.close();
} catch (IOException e) {
System.err.println("파일을 읽을 수 없습니다: " + e.getMessage());
// 회복 로직: 기본값 사용, 재시도 등
}
}
// try-with-resources — 자동 close (Java 7+)
public String readFileContent(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
StringBuilder content = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
content.append(line).append("\n");
}
return content.toString();
}
// IOException은 호출자에게 전파됨
}
주요 Checked Exception
| 예외 클래스 | 발생 상황 |
|---|---|
IOException | 파일/스트림 입출력 오류 |
FileNotFoundException | 파일이 존재하지 않음 |
SQLException | DB 접근 오류 |
ClassNotFoundException | 클래스를 찾을 수 없음 |
InterruptedException | 스레드 인터럽트 |
ParseException | 파싱 오류 |
Unchecked Exception (RuntimeException)
RuntimeException을 상속하는 예외입니다. 컴파일러가 처리를 강제하지 않으며, 주로 프로그래밍 오류를 나타냅니다.
특징
try-catch나throws없이도 컴파일 가능- 주로 개발자의 실수로 발생
- 실행 시점에 발견됨
// NullPointerException — null 참조 접근
String str = null;
str.length(); // NullPointerException
// ArrayIndexOutOfBoundsException — 배열 범위 초과
int[] arr = new int[5];
arr[10] = 1; // ArrayIndexOutOfBoundsException
// ClassCastException — 잘못된 타입 캐스팅
Object obj = "hello";
Integer num = (Integer) obj; // ClassCastException
// IllegalArgumentException — 잘못된 인자 (개발자가 직접 던지는 경우)
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("나이는 0-150 사이여야 합니다: " + age);
}
this.age = age;
}
// IllegalStateException — 잘못된 상태에서 메서드 호출
public void processOrder() {
if (orderStatus != OrderStatus.PENDING) {
throw new IllegalStateException("대기 상태의 주문만 처리할 수 있습니다.");
}
// 처리 로직
}
주요 Unchecked Exception
| 예외 클래스 | 발생 상황 |
|---|---|
NullPointerException | null 참조에 접근 |
ArrayIndexOutOfBoundsException | 배열 인덱스 초과 |
StringIndexOutOfBoundsException | 문자열 인덱스 초과 |
ClassCastException | 잘못된 타입 캐스팅 |
IllegalArgumentException | 잘못된 메서드 인자 |
IllegalStateException | 잘못된 상태에서 호출 |
NumberFormatException | 숫자 파싱 오류 |
StackOverflowError | 스택 오버플로우 |
Error
JVM 수준의 심각한 오류로, 일반적으로 애플리케이션에서 처리해서는 안 됩니다.
// OutOfMemoryError — 힙 메모리 부족
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // 결국 OutOfMemoryError
// StackOverflowError — 무한 재귀
void recurse() {
recurse(); // StackOverflowError
}
Checked vs Unchecked 선택 기준
// Checked Exception: 호출자가 회복할 수 있는 경우
public User findUser(long userId) throws UserNotFoundException {
User user = userRepository.findById(userId);
if (user == null) {
throw new UserNotFoundException("사용자를 찾을 수 없습니다: " + userId);
}
return user;
}
// Unchecked Exception: 프로그래밍 오류 또는 회복 불가능한 경우
public void transfer(Account from, Account to, BigDecimal amount) {
if (from == null || to == null) {
throw new IllegalArgumentException("계좌는 null일 수 없습니다."); // Unchecked
}
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("이체 금액은 0보다 커야 합니다."); // Unchecked
}
// 이체 로직...
}
예외 전환 (Exception Translation)
하위 계층의 예외를 상위 계층에 적합한 예외로 변환합니다.
public User findUserByEmail(String email) {
try {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE email = ?",
userRowMapper, email
);
} catch (EmptyResultDataAccessException e) {
throw new UserNotFoundException("이메일로 사용자를 찾을 수 없음: " + email, e);
} catch (DataAccessException e) {
throw new RepositoryException("데이터 접근 오류", e); // 원인 보존
}
}
Spring 트랜잭션과 예외
Spring은 기본적으로 RuntimeException(Unchecked)에서만 트랜잭션을 롤백합니다.
@Service
public class OrderService {
@Transactional
public void placeOrder(OrderRequest request) throws InsufficientStockException {
// InsufficientStockException이 Checked Exception이라면
// 트랜잭션이 롤백되지 않음!
}
// Checked Exception도 롤백하려면 rollbackFor 지정
@Transactional(rollbackFor = InsufficientStockException.class)
public void placeOrderWithRollback(OrderRequest request) throws InsufficientStockException {
// 이제 InsufficientStockException 발생 시에도 롤백됨
}
// RuntimeException은 기본으로 롤백됨
@Transactional
public void placeOrderUnchecked(OrderRequest request) {
// RuntimeException 발생 시 자동 롤백
throw new IllegalStateException("재고 부족");
}
}
비교 정리
| 구분 | Checked Exception | Unchecked Exception | Error |
|---|---|---|---|
| 상속 | Exception | RuntimeException | Error |
| 컴파일러 강제 | O | X | X |
| 처리 방식 | try-catch / throws | 선택적 | 처리 불권장 |
| 발생 원인 | 외부 환경 오류 | 프로그래밍 오류 | JVM 오류 |
| Spring 롤백 | X (기본) | O (기본) | O (기본) |
| 예시 | IOException | NullPointerException | OutOfMemoryError |