1. 다중 catch: 서로 다른 예외 처리
실제 프로그램에서는 동일한 코드 블록이 여러 타입의 예외를 던질 수 있습니다. 예를 들어, 파일을 읽을 때는 FileNotFoundException이나 IOException이 발생할 수 있고, 데이터 파싱 시에는 NumberFormatException까지 발생할 수 있습니다. 각 상황을 올바르게 처리하려면 Java에서는 여러 개의 catch 블록을 연달아 사용할 수 있습니다.
문법
try {
// 위험한 작업
} catch (FileNotFoundException e) {
System.out.println("파일을 찾을 수 없음: " + e.getMessage());
} catch (IOException e) {
System.out.println("입출력 오류: " + e.getMessage());
} catch (NumberFormatException e) {
System.out.println("잘못된 숫자 형식: " + e.getMessage());
}
중요 포인트:
catch 블록은 더 구체적인 것에서 더 일반적인 것으로 배치해야 합니다! 처음에 catch (Exception e)를 작성하면 나머지 블록은 도달 불가능해지고 컴파일 오류가 발생합니다.
왜 중요할까요?
- 구체적인 처리: 오류에 따라 다르게 반응할 수 있습니다(예: 다른 파일을 선택하도록 안내하거나 입력을 다시 받기).
- 가독성: 어떤 오류를 예상하고 어떻게 처리하는지 한눈에 보입니다.
2. 여러 타입을 한 번에 잡는 catch (multi-catch)
때로는 서로 다른 예외에 대해 동일한 처리가 필요합니다. 예를 들어, 파일 읽기 오류든 숫자 변환 오류든 상관없이 오류 메시지를 출력하거나 로깅만 하고 싶을 수 있습니다.
Java 7부터 multi-catch 문법이 도입되어 하나의 블록에서 여러 예외 타입을 합쳐 처리할 수 있습니다:
try {
// 위험한 작업
} catch (IOException | NumberFormatException e) {
System.out.println("파일 또는 숫자 처리 중 오류: " + e.getMessage());
}
작동 방식:
- 세로 막대(|)로 예외 타입을 나열합니다.
- 변수 e의 정적 타입은 가장 공통된 상위 클래스(보통 Exception)입니다.
- 블록 안에서 변수 e에 새 값을 할당할 수 없습니다 — 암묵적으로 final로 간주됩니다.
예시:
try {
BufferedReader reader = new BufferedReader(new FileReader("numbers.txt"));
String line = reader.readLine();
int number = Integer.parseInt(line);
System.out.println("숫자: " + number);
reader.close();
} catch (IOException | NumberFormatException e) {
System.out.println("오류: " + e.getMessage());
}
3. 예외 처리 모범 사례
예외를 억누르지 마세요
나쁨:
try {
// 위험한 작업
} catch (Exception e) {
// 아무 것도 하지 않음!
}
왜 나쁠까요?
비어 있는 catch 블록은 오류를 “묵살”하여 장애의 원인 정보를 잃게 만들고, 프로그램을 디버깅하기 어렵게 합니다.
더 나은 방법: 최소한 메시지를 출력하거나 오류를 로깅하세요.
catch (Exception e) {
e.printStackTrace();
}
예외를 처리할 수 없다면 — 상위로 전파하세요:
catch (IOException e) {
throw e; // 또는 throw new RuntimeException(e);
}
가장 구체적인 예외를 던지세요
직접 메서드나 API를 작성할 때는 Exception이나 RuntimeException처럼 포괄적인 타입보다 최대한 구체적인 예외를 던지세요. 그러면 코드와 인터페이스가 다른 개발자에게 더 명확해집니다.
예시:
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("잔액이 부족합니다");
}
// ...
}
프로그램 흐름 제어에 예외를 사용하지 마세요
예외는 예외적인 상황—즉, 정상적인 실행 경로에서 발생하지 않아야 하는 오류—을 처리하기 위한 것입니다. 예외를 일반적인 로직(예: 루프 탈출이나 조건 검사)에 사용하지 마세요.
나쁨:
try {
while (true) {
String line = reader.readLine();
if (line == null) break;
// 한 줄 처리
}
} catch (Exception e) {
// 예외를 루프 종료 용도로 사용 — 나쁨!
}
좋음:
String line;
while ((line = reader.readLine()) != null) {
// 한 줄 처리
}
4. 실전 예제
예: 서로 다른 오류를 다르게 처리하기
try {
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
int number = Integer.parseInt(line);
System.out.println("숫자: " + number);
reader.close();
} catch (FileNotFoundException e) {
System.out.println("파일을 찾을 수 없습니다!");
} catch (IOException e) {
System.out.println("파일 읽기 오류!");
} catch (NumberFormatException e) {
System.out.println("파일 내용이 숫자가 아닙니다!");
}
예: 동일한 처리를 위한 multi-catch
try {
// 위험한 작업
} catch (IOException | NumberFormatException e) {
System.out.println("오류: " + e.getMessage());
}
5. 중요한 포인트와 흔한 실수
오류 №1: catch 블록의 순서가 잘못됨. 먼저 더 구체적인 예외, 다음으로 일반적인 예외가 와야 합니다. 그렇지 않으면 넓은 블록까지 실행 흐름이 결코 도달하지 않습니다.
오류 №2: 상하위(관련) 타입을 multi-catch에 함께 사용. 예를 들어, catch (IOException | Exception e)는 컴파일되지 않습니다. IOException은 Exception의 하위 클래스이기 때문입니다.
오류 №3: multi-catch에서 변수 e를 변경하려고 함. 이 경우 e는 final로 간주되므로 새 값을 할당할 수 없습니다.
오류 №4: 비어 있는 catch 블록. 오류를 무시하면 “조용한” 버그로 이어집니다. 최소한 메시지를 출력하거나 예외를 로깅하세요.
오류 №5: 모든 Exception을 한꺼번에 잡기. catch (Exception e) 사용은 실제 문제를 가려 디버깅을 어렵게 만듭니다. 예상되는 예외만 잡는 것이 좋습니다.
GO TO FULL VERSION