1. 클래스 Throwable: 모든 예외의 루트
지금부터 Java의 예외 시스템이 어떻게 구성되어 있는지 자세히 살펴보겠습니다. Throwable이 무엇인지, Exception과 Error는 어떻게 다른지, 그리고 “체크 예외”와 “언체크 예외”는 무엇을 의미하는지 알아봅니다. 이는 프로그램에서 올바르게 오류를 처리하기 위한 기초입니다.
Java에서 모든 예외와 오류는 java.lang.Throwable 클래스를 상속하는 객체입니다.
Throwable은 Java에서 문제를 처리하는 전체 계층의 “조상”입니다.
개략적으로:
Throwable
├── Exception
└── Error
Throwable은 Java에서 “던질” 수 있고(throw) “잡을” 수 있는(catch) 모든 것의 기본 클래스입니다. 직접 사용하는 클래스가 아니며, 보다 구체적인 오류 타입을 위한 기반으로 쓰입니다.
Exception — 일상적인(업무) 오류용
Exception은 프로그램에서 발생할 수 있고 처리해야 하는 모든 예외의 기본 클래스입니다. 이는 파일, 네트워크, 입출력, 사용자 입력 등과 관련된 “업무” 오류입니다. 여러분의 대부분의 try-catch는 Exception의 하위 클래스와 함께 동작합니다.
예시:
- IOException — 파일 또는 네트워크 작업 중 오류.
- SQLException — 데이터베이스 작업 중 오류.
- FileNotFoundException — 파일을 찾을 수 없음.
Error — JVM의 치명적 오류용
Error는 Java 가상 머신(JVM) 수준에서 발생하는 오류의 기본 클래스입니다. 보통 프로그램이 처리할 수 없고 처리해서도 안 되는 치명적 장애입니다. Error가 발생하면 애플리케이션은 대개 작업을 계속할 수 없습니다.
예시:
- OutOfMemoryError — 메모리가 고갈됨.
- StackOverflowError — 스택 오버플로(예: 무한 재귀로 인해).
- NoClassDefFoundError — 필요한 클래스를 찾을 수 없음.
중요:
Error를 잡아 처리하는 것은 거의 항상 좋지 않은 선택입니다. 이는 여러분의 코드 문제가 아니라 실행 환경의 문제입니다.
2. Checked vs Unchecked exceptions: 무슨 뜻인가요?
Java의 모든 예외는 두 가지 큰 그룹으로 나뉩니다:
체크 예외(Checked)
무엇인가? 컴파일러가 반드시 처리하거나 명시적으로 더 위로 전파하도록 강제하는 예외입니다.
언제 발생하나? 보통 외부 리소스와 관련됩니다: 파일, 네트워크, 데이터베이스, 사용자 입력 등.
어떻게 처리하나? 코드를 try-catch로 감싸거나, 메서드 시그니처에 throws를 추가해야 합니다.
예시: IOException, SQLException, FileNotFoundException
예제:
public void readFile(String path) throws IOException {
FileReader reader = new FileReader(path); // IOException을 던질 수 있음
// ...
}
처리하거나 전파하지 않으면 — 프로그램은 컴파일되지 않습니다!
언체크 예외(Unchecked)
무엇인가? 컴파일러가 강제하지 않는 예외입니다.
언제 발생하나? 보통 프로그램 로직의 오류입니다: 0으로 나누기, 배열 범위 초과, null 참조 접근 등.
어떻게 처리하나? 잡을 수는 있지만 필수는 아닙니다. 이런 오류는 사전 검증으로 예방하는 것이 좋습니다.
계층에서의 위치? 모두 RuntimeException을 상속합니다.
예시: NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException, ArithmeticException
예제:
int[] arr = {1, 2, 3};
System.out.println(arr[10]); // ArrayIndexOutOfBoundsException
컴파일러는 이 예외를 잡도록 강제하지 않습니다 — 그러나 오류가 발생하면 프로그램은 “크래시”됩니다.
3. 전체 계층을 한 그림으로
graph TD
Throwable --> Error
Throwable --> Exception
Exception --> RuntimeException
Exception --> CheckedExceptions["(기타 체크 예외)"]
Error --> OutOfMemoryError
Error --> StackOverflowError
RuntimeException --> NullPointerException
RuntimeException --> IndexOutOfBoundsException
RuntimeException --> IllegalArgumentException
%% 스타일
style Throwable fill:#ffa64d,color:#000
style Exception fill:#ffa64d,color:#000
style CheckedExceptions fill:#ffa64d,color:#000
style Error fill:#ff4d4d,color:#fff
style OutOfMemoryError fill:#ff4d4d,color:#fff
style StackOverflowError fill:#ff4d4d,color:#fff
style RuntimeException fill:#4dff88,color:#000
style NullPointerException fill:#4dff88,color:#000
style IndexOutOfBoundsException fill:#4dff88,color:#000
style IllegalArgumentException fill:#4dff88,color:#000
표: 주요 차이점
| 그룹 | 부모 클래스 | 처리가 필요한가? | 예시 |
|---|---|---|---|
| 체크 예외 | |
예 | |
| 언체크 | |
아니오 | |
| Error | |
아니오 | |
4. 코드에서는 어떻게 보이나요?
Checked Exception: 파일 예시
import java.io.*;
public class FileDemo {
public static void main(String[] args) {
try {
FileReader reader = new FileReader("nofile.txt"); // FileNotFoundException (checked)
int data = reader.read();
System.out.println(data);
reader.close();
} catch (IOException e) {
System.out.println("파일 작업 중 오류: " + e.getMessage());
}
}
}
컴파일러는 IOException 처리를 요구합니다!
Unchecked Exception: 0으로 나누기 예시
public class ExceptionDemo {
public static void main(String[] args) {
int a = 10;
int b = 0;
int c = a / b; // ArithmeticException (unchecked)
System.out.println("결과: " + c);
}
}
컴파일러는 처리를 요구하지 않지만, 프로그램은 “크래시”됩니다.
5. 왜 예외 계층이 필요한가?
- 처리의 유연성: 특정 오류(FileNotFoundException)부터 전체 그룹(IOException 또는 Exception)까지 선택적으로 잡을 수 있습니다.
- 코드 재사용: 동일한 유형의 오류를 중앙집중식으로 처리할 수 있습니다.
- 코드의 간결함: 사소한 검사로 본래의 비즈니스 로직이 어수선해지지 않습니다.
예제:
try {
// 위험한 코드
} catch (FileNotFoundException e) {
System.out.println("파일을 찾을 수 없습니다!");
} catch (IOException e) {
System.out.println("입출력 오류!");
} catch (Exception e) {
System.out.println("문제가 발생했습니다: " + e.getMessage());
}
6. 예외 처리 시 자주 하는 실수
오류 № 1: 예외를 무시함. catch (Exception e) {}처럼 비워 두는 것은 좋지 않습니다! 오류 원인에 대한 정보를 잃습니다.
오류 № 2: 너무 많이 잡음. catch (Exception e)는 예상하지 못한 것까지 모조리 잡습니다. 처리할 수 있는 예외만 잡는 것이 좋습니다.
오류 № 3: Error를 잡음. 저수준 코드를 작성하는 것이 아니라면 Error를 잡지 마세요. 이는 JVM의 문제이지 여러분 프로그램의 문제가 아닙니다.
오류 № 4: checked와 unchecked를 구분하지 않음. 모든 예외가 같은 것은 아닙니다! checked는 처리해야 하고(Exception), unchecked는 필수가 아닙니다(RuntimeException 및 그 하위 클래스).
오류 № 5: 예외에 정보를 추가하지 않음. 사용자 정의 예외를 만들 때는 항상 정보가 담긴 메시지를 추가하세요.
GO TO FULL VERSION