8.1 예외 처리 소개 (Exception Handling Overview)
이 가이드는 Java 21 버전을 기준으로 작성되었습니다. 최신 규격은 공식 Java Documentation을 참고하세요.
프로그램을 만들다 보면 필연적으로 다양한 오류를 만나게 됩니다. 사용자가 숫자를 입력해야 하는 곳에 문자를 입력하거나, 읽어야 할 파일이 삭제되어 없거나, 네트워크 연결이 끊어지는 등 다양한 예외 상황이 발생할 수 있습니다.
자바에서 예외 처리(Exception Handling) 란, 이처럼 프로그램 실행 중 발생할 수 있는 예기치 못한 에러 상황에 대비한 코드를 미리 작성하여, 프로그램의 비정상적인 종료를 막고 정상적인 실행 상태를 유지 하도록 하는 필수적인 작업입니다.
1. 에러(Error) vs 예외(Exception)
자바에서는 프로그램 실행 시 발생할 수 있는 문제를 크게 두 가지로 분류합니다.
에러(Error)
메모리 부족(OutOfMemoryError)이나 스택 오버플로우(StackOverflowError)처럼 시스템 자체가 마비될 정도로 심각하고 복구할 수 없는 수준의 오류입니다. 발생하면 프로그램의 비정상 종료를 막을 방법이 없으므로, 개발자가 코드 레벨에서 수습할 수 있는 영역이 아닙니다.
// StackOverflowError 예시 - 재귀 호출이 끝없이 이어질 때 발생
public class ErrorExample {
public static void infiniteRecursion() {
infiniteRecursion(); // 자기 자신을 끊임없이 호출 → StackOverflowError!
}
public static void main(String[] args) {
infiniteRecursion();
}
}
// 실행 결과: Exception in thread "main" java.lang.StackOverflowError
예외(Exception)
사용자의 잘못된 조작이나 개발자의 코딩 실수로 인해 발생하는 다소 가벼운 형태의 오류입니다. 예외 처리 구문(try-catch)을 통해 코드 레벨에서 충분히 수습 및 복구 가 가능합니다.
우리가 공부하고 코드로 방어해야 하는 부분은 바로 이 예외(Exception) 입니다.
2. 예외 클래스의 계층 구조
자바에서는 모든 예외와 에러도 객체로 취급되며, 최상위 조상인 java.lang.Object 클래스를 상속받습니다.
Object
└── Throwable
├── Error ← 시스템 수준 심각한 오류 (처리 불가)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception ← 프로그램 수준 오류 (처리 가능)
├── IOException ← Checked Exception
├── SQLException ← Checked Exception
├── ClassNotFoundException ← Checked Exception
└── RuntimeException ← Unchecked Exception
├── NullPointerException
├── ArrayIndexOutOfBoundsException
├── ClassCastException
├── NumberFormatException
└── ArithmeticException
3. Checked Exception vs Unchecked Exception
예외 클래스들은 크게 두 부류로 나뉩니다.
| 구분 | Checked Exception | Unchecked Exception |
|---|---|---|
| 상속 | Exception 직계 자손 | RuntimeException 자손 |
| 컴파일러 강제 | 예외 처리 필수 (안 하면 컴파일 에러) | 예외 처리 선택 사항 |
| 주요 원인 | 외부 환경 (파일 없음, 네트워크 오류) | 프로그래머 실수 (null 접근, 잘못된 형변환) |
| 대표 예시 | IOException, SQLException | NullPointerException, ClassCastException |
import java.io.FileReader;
import java.io.IOException;
public class CheckedExample {
public static void main(String[] args) {
// Checked Exception: 컴파일러가 반드시 처리를 요구합니다
// try-catch 없이 쓰면 컴파일 에러 발생!
try {
FileReader reader = new FileReader("없는파일.txt");
} catch (IOException e) {
System.out.println("파일을 찾을 수 없습니다: " + e.getMessage());
}
}
}
4. 자주 발생하는 예외 목록
실무에서 가장 자주 만나게 되는 예외들을 미리 알아두면 디버깅 시간을 크게 줄일 수 있습니다.
NullPointerException (NPE)
null인 참조 변수의 멤버를 접근할 때 발생합니다. 자바 개발자가 가장 많이 만나는 예외입니다.
public class NullPointerExam {
public static void main(String[] args) {
String name = null;
// null인 변수에 메서드를 호출하면 NullPointerException 발생!
try {
int len = name.length(); // 에러 발생!
} catch (NullPointerException e) {
System.out.println("name이 null입니다. 초기화가 필요합니다.");
// Java 14+: e.getMessage()가 더 자세한 원인을 알려줍니다
}
// 방어적 코딩: null 체크 후 호출
if (name != null) {
System.out.println("길이: " + name.length());
}
}
}
ArrayIndexOutOfBoundsException
배열의 유효 범위를 벗어난 인덱스에 접근할 때 발생합니다.
public class ArrayExam {
public static void main(String[] args) {
int[] arr = {10, 20, 30}; // 인덱스 0, 1, 2
try {
System.out.println(arr[5]); // 인덱스 5는 없음! 에러 발생
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("배열 범위를 벗어났습니다: " + e.getMessage());
}
// 올바른 배열 순회
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
NumberFormatException
문자열을 숫자로 변환할 때 변환 불가능한 형식이면 발생합니다.
public class NumberFormatExam {
public static void main(String[] args) {
String[] inputs = {"123", "abc", "45.6", ""};
for (String input : inputs) {
try {
int number = Integer.parseInt(input);
System.out.println("변환 성공: " + number);
} catch (NumberFormatException e) {
System.out.println("변환 실패 [" + input + "]: 숫자 형식이 아닙니다");
}
}
}
}
실행 결과:
변환 성공: 123
변환 실패 [abc]: 숫자 형식이 아닙니다
변환 실패 [45.6]: 숫자 형식이 아닙니다
변환 실패 []: 숫자 형식이 아닙니다
ClassCastException
형변환이 불가능한 타입으로 강제 변환할 때 발생합니다.
public class ClassCastExam {
public static void main(String[] args) {
Object obj = "Hello"; // String 객체를 Object 타입으로 저장
try {
// String을 Integer로 형변환 시도 → 불가능!
Integer num = (Integer) obj;
} catch (ClassCastException e) {
System.out.println("형변환 실패: " + e.getMessage());
}
// instanceof로 미리 확인하는 안전한 방법
if (obj instanceof String str) { // Java 16+ 패턴 매칭
System.out.println("문자열 길이: " + str.length());
}
}
}
5. 예외 처리가 중요한 이유
- 프로그램 안정성: 언제 터질지 모르는 치명적인 버그로부터 프로그램이 죽지 않도록 방탄(Bullet-proof) 코드를 만듭니다.
- 사용자 경험: 에러가 발생했을 때 무서운 스택 추적(Stack Trace) 대신 "현재 서비스를 이용할 수 없습니다"처럼 친절한 안내 메시지를 출력할 수 있습니다.
- 디버깅 지원: 문제 발생 시 관련 로그(Log)를 안전하게 기록하여 개발자가 추후 버그를 쉽게 수정하도록 돕습니다.
// 예외 처리 없는 코드 (위험)
public class UnsafeCode {
public static void main(String[] args) {
String input = "abc";
int number = Integer.parseInt(input); // 예외 발생 → 프로그램 죽음!
System.out.println("결과: " + number); // 절대 실행 안 됨
}
}
// 예외 처리 있는 코드 (안전)
public class SafeCode {
public static void main(String[] args) {
String input = "abc";
try {
int number = Integer.parseInt(input);
System.out.println("결과: " + number);
} catch (NumberFormatException e) {
System.out.println("올바른 숫자를 입력해주세요.");
// 프로그램은 계속 실행됩니다!
}
System.out.println("프로그램이 정상적으로 계속 실행됩니다.");
}
}
6. 예외 처리 방법 3가지
자바에서 예외를 처리하는 방법은 크게 세 가지입니다.
| 방법 | 문법 | 설명 |
|---|---|---|
| try-catch로 직접 처리 | try { } catch { } | 예외가 발생한 곳에서 바로 처리 |
| throws로 책임 전가 | void method() throws IOException | 상위 호출자에게 처리 위임 |
| throw로 직접 발생 | throw new Exception() | 의도적으로 예외 객체를 생성해 던짐 |
import java.io.IOException;
public class ExceptionMethodsExample {
// 방법 2: throws로 예외 처리를 호출자에게 위임
public static void readFile(String path) throws IOException {
// 이 메서드는 예외를 직접 처리하지 않고 호출한 쪽에 넘김
throw new IOException("파일을 읽을 수 없습니다: " + path);
}
// 방법 3: throw로 직접 예외 발생
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다");
}
return a / b;
}
public static void main(String[] args) {
// 방법 1: try-catch로 직접 처리
try {
readFile("없는파일.txt"); // throws IOException 메서드 호출
} catch (IOException e) {
System.out.println("파일 오류: " + e.getMessage());
}
try {
int result = divide(10, 0);
} catch (ArithmeticException e) {
System.out.println("계산 오류: " + e.getMessage());
}
System.out.println("프로그램이 정상 종료됩니다.");
}
}
실행 결과:
파일 오류: 파일을 읽을 수 없습니다: 없는파일.txt
계산 오류: 0으로 나눌 수 없습니다
프로그램이 정상 종료됩니다.
7. 예외 정보 확인 메서드
예외 객체(e)를 통해 오류의 세부 정보를 확인할 수 있습니다.
public class ExceptionInfoExample {
public static void main(String[] args) {
try {
int[] arr = new int[3];
arr[10] = 100; // ArrayIndexOutOfBoundsException 발생
} catch (ArrayIndexOutOfBoundsException e) {
// 1. getMessage(): 예외의 간단한 메시지 반환
System.out.println("메시지: " + e.getMessage());
// 2. toString(): 예외 클래스명 + 메시지
System.out.println("toString: " + e.toString());
// 3. printStackTrace(): 예외 발생 경로 전체를 출력 (디버깅 필수)
System.out.println("--- 스택 추적 ---");
e.printStackTrace();
// 4. getClass().getName(): 예외 클래스 이름
System.out.println("예외 클래스: " + e.getClass().getName());
}
}
}
printStackTrace()는 개발/디버깅 단계에서는 매우 유용하지만, 운영(Production) 환경에서는 민감한 시스템 정보가 노출될 수 있으므로 로그 프레임워크(Logback, Log4j 등)를 통해 로그 파일에 기록하는 것을 권장합니다.
8. 실전 예제: 사용자 입력 안전하게 처리하기
import java.util.Scanner;
public class SafeInputExample {
// 문자열을 안전하게 정수로 변환하는 유틸 메서드
public static int parseIntSafe(String input, int defaultValue) {
try {
return Integer.parseInt(input.trim());
} catch (NumberFormatException e) {
System.out.println("경고: '" + input + "'은 유효한 숫자가 아닙니다. 기본값 " + defaultValue + " 사용.");
return defaultValue;
} catch (NullPointerException e) {
System.out.println("경고: null 값입니다. 기본값 " + defaultValue + " 사용.");
return defaultValue;
}
}
public static void main(String[] args) {
String[] testInputs = {"42", " 100 ", "abc", null, "3.14", "999"};
int total = 0;
for (String input : testInputs) {
int value = parseIntSafe(input, 0);
total += value;
System.out.println("입력: " + input + " → 변환: " + value);
}
System.out.println("합계: " + total);
}
}
실행 결과:
입력: 42 → 변환: 42
입력: 100 → 변환: 100
경고: 'abc'은 유효한 숫자가 아닙니다. 기본값 0 사용.
입력: abc → 변환: 0
경고: null 값입니다. 기본값 0 사용.
입력: null → 변환: 0
경고: '3.14'은 유효한 숫자가 아닙니다. 기본값 0 사용.
입력: 3.14 → 변환: 0
입력: 999 → 변환: 999
합계: 1141
실무에서는 예외를 단순히 catch (Exception e)로 뭉뚱그려 잡는 것은 안티패턴입니다. 가능한 한 구체적인 예외 타입 을 명시하여 각 상황에 맞는 처리를 하는 것이 좋은 코드입니다. 또한 catch 블록에서 아무것도 하지 않는 "빈 catch 블록"은 버그를 숨기는 최악의 습관입니다.
다음 장에서는 자바에서 예외를 통제하는 가장 기본이자 핵심적인 무기인 try-catch 블록에 대해 자세히 알아보겠습니다.