Java의 예외 계층구조는 다음과 같습니다:
Throwable
├── Error
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── ...
└── Exception
├── IOException (Checked)
├── SQLException (Checked)
└── RuntimeException (Unchecked)
├── NullPointerException
├── IllegalArgumentException
└── ...
Throwable: 모든 예외의 최상위 클래스Error: 시스템 레벨의 심각한 문제Exception: 애플리케이션 레벨의 문제RuntimeException: Unchecked Exception의 최상위 클래스public class CheckedExceptionExample {
public void readFile(String path) throws IOException {
Files.readAllLines(Paths.get(path)); // IOException 발생 가능
}
public void handleFile(String path) {
try {
readFile(path);
} catch (IOException e) {
// 복구 가능한 예외 처리
logger.error("파일 읽기 실패", e);
// 대체 로직 수행
}
}
}
public class UnCheckedExceptionExample {
public void divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("0으로 나눌 수 없습니다.");
}
return a / b;
}
// throws 선언이 필요없음
public void processNumber(int num) {
if (num < 0) {
throw new IllegalArgumentException("음수는 처리할 수 없습니다.");
}
}
}
| 특징 | Checked Exception | Unchecked Exception |
|---|---|---|
| 처리 여부 | 컴파일러가 체크 | 컴파일러가 체크하지 않음 |
| 처리 방법 | try-catch 또는 throws 필수 | 선택적 처리 |
| 트랜잭션 처리 | 롤백되지 않음 | 롤백됨 |
| 사용 시점 | 비즈니스 로직상 예외 상황 | 프로그래밍 오류 |
public class BusinessService {
public void processOrder(Order order) throws InsufficientStockException {
if (order.getQuantity() > stockRepository.getAvailableStock()) {
throw new InsufficientStockException("재고가 부족합니다.");
}
// 주문 처리 로직
}
}
public class ValidationService {
public void validateUser(User user) {
if (user == null) {
throw new IllegalArgumentException("사용자 정보가 null입니다.");
}
if (user.getName() == null || user.getName().isEmpty()) {
throw new IllegalArgumentException("사용자 이름은 필수입니다.");
}
}
}
@Service
public class UserService {
public User findUser(Long id) {
try {
return userRepository.findById(id);
} catch (SQLException e) {
// SQL 예외를 비즈니스 예외로 변환
throw new UserNotFoundException("사용자를 찾을 수 없습니다.", e);
}
}
}
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public BusinessException(ErrorCode errorCode, Throwable cause) {
super(errorCode.getMessage(), cause);
this.errorCode = errorCode;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
ErrorResponse response = ErrorResponse.of(e.getErrorCode());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Service
@Transactional
public class OrderService {
public Order createOrder(OrderRequest request) {
validateOrder(request); // Unchecked Exception
try {
// 외부 결제 시스템 호출
paymentClient.processPayment(request.getPaymentInfo()); // Checked Exception
} catch (PaymentException e) {
throw new OrderException("주문 처리 중 결제 오류 발생", e);
}
return orderRepository.save(new Order(request));
}
private void validateOrder(OrderRequest request) {
if (request == null) {
throw new IllegalArgumentException("주문 정보가 없습니다.");
}
// 기타 검증 로직
}
}
Q: Checked Exception과 Unchecked Exception의 차이점을 설명해주세요.
A: Checked Exception과 Unchecked Exception의 주요 차이점:
컴파일 시점 처리 여부
사용 목적
트랜잭션 처리
Q: Exception 처리 시 Runtime Exception을 사용하는 것이 좋을까요, Checked Exception을 사용하는 것이 좋을까요?
A: 각 Exception 유형의 적절한 사용 상황:
Runtime Exception 사용이 적절한 경우
Checked Exception 사용이 적절한 경우
Q: 커스텀 예외를 만들 때 고려해야 할 점은 무엇인가요?
A: 커스텀 예외 설계 시 고려사항:
계층 구조
명명 규칙
정보 제공
구현 세부사항