Java 예외 처리 마스터: Try-Catch, Finally, 클래스 계층 구조
Java에서 예외란?
Java 예외(Exception)는 프로그램 명령어 실행을 방해하여 정상적인 제어 흐름을 깨뜨리는 사건입니다. 메서드 내에서 발생한 오류 정보를 캡슐화한 객체로, 런타임 시스템에 전달됩니다. Java에서 예외는 주로 표준 제어 흐름으로 처리할 수 없는 다양한 오류 조건을 알립니다.
이 튜토리얼에서 배울 내용:
- 예외의 정의
- 예외가 중요한 이유
- 효과적인 예외 처리 방법
- try-catch 구조
- Java 예외 클래스 계층 구조
- finally 블록의 역할
Java의 에러는 두 큰 범주로 나뉩니다:
- 컴파일 타임 에러
- 런타임 에러
컴파일 타임 에러는 다시:
문법 에러 예:
int a; 선언은 올바르지만 in a;는 컴파일러가 에러 보고.
예를 들어 int a;를 한 번 선언하고 같은 스코프에서 나중에 다시 int a;를 선언하려 하면 컴파일러 에러가 발생하며 빌드 시 강조 표시됩니다.
런타임 에러 예
런타임 에러는 예외로 나타나며 정상 실행 흐름을 방해하는 사건입니다. 대표적으로 ArithmeticException, NullPointerException, 0으로 나누기 등이 있습니다.
이러한 예외는 개발자의 직접적 통제를 벗어난 조건에서 발생합니다.

왜 예외 처리가 필요한가?
원격 서버에 접근하는 프로그램을 생각해 보세요. 개발 중에는 서버에 도달 가능하여 코드가 원활히 실행됩니다. 하지만 운영 환경에서는 서버가 다운되어 프로그램 연결 시도 시 예외가 발생할 수 있습니다.

코드의 자연적 흐름만으로는 부족합니다. 견고한 프로그램은 예외적 상황을 예상하고 우아하게 복구합니다.
예외 처리 방법
예외 처리는 예외적 상황을 예상하고 대체 로직을 제공하는 관행입니다. 일반적인 전략은 기본 연산을 시도하고, 실패 시 백업으로 전환하는 것입니다.

전통적인 if-else 구조로 여러 예외 타입을 처리하려 하면 금방 복잡해집니다:
class Connect {
if (serverIsUp()) {
// 주 서버 연결
} else {
// 백업 서버 연결
}
}
Try-Catch 블록
Java는 예외 처리를 위한 내장 메커니즘을 제공합니다. 예외를 던질 수 있는 코드를 try 블록에, 대응 복구 코드를 하나 이상의 catch 블록에 배치합니다.

예를 들어 try 블록이 주 서버 연결을 시도하고, catch 블록이 폴백 로직을 처리합니다.
서버가 작동 중이면 catch 블록은 건너뛰고, 서버가 다운되면 예외가 던져지며 catch 블록이 실행됩니다.

try-catch 쌍의 문법:
try {
// 예외를 던질 수 있는 문장들
} catch (ExceptionType name) {
// 복구 문장들
}
실전 예제
1단계: 다음 코드를 IDE에 복사.
class JavaException {
public static void main(String args[]) {
int d = 0;
int n = 20;
int fraction = n / d; // ArithmeticException 발생
System.out.println("End Of Main");
}
}
2단계: 컴파일하고 실행. 프로그램은 ArithmeticException을 던지고 5번째 줄에서 종료.
3단계: try-catch 사용하도록 코드 수정:
class JavaException {
public static void main(String args[]) {
int d = 0;
int n = 20;
try {
int fraction = n / d;
System.out.println("This line will not be executed");
} catch (ArithmeticException e) {
System.out.println("Caught exception: " + e);
}
System.out.println("End Of Main");
}
}
4단계: 다시 컴파일하고 실행. 출력은 예외가 우아하게 처리되고 프로그램이 마지막 출력문까지 계속됨을 보여줍니다.

잡힌 ArithmeticException 객체 e는 로깅이나 복구에 사용할 수 있는 상세 정보를 담고 있습니다.
예외 클래스 계층 구조
Java의 예외 계층은 Throwable 클래스에서 시작하며, 두 직접 서브클래스 Error와 Exception을 가집니다. Error는 복구 불가능한 조건(예: OutOfMemoryError)을 나타내며, Exception은 프로그래밍적으로 처리 가능한 조건을 다룹니다.

- RuntimeException (예: NullPointerException, ArithmeticException) – 컴파일 타임에 감지되지 않는 언체크 예외
- IOException – 입출력 연산 중 발생하는 문제
- InterruptedException – 스레드가 인터럽트될 때 던져짐
중첩 Try-Catch 예제
다음 코드 복사:
class JavaException {
public static void main(String args[]) {
try {
int d = 1;
int n = 20;
int fraction = n / d;
int g[] = {1};
g[20] = 100; // ArrayIndexOutOfBoundsException
} catch (ArithmeticException e) {
System.out.println("Caught ArithmeticException: " + e);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Caught ArrayIndexOutOfBoundsException: " + e);
}
System.out.println("End Of Main");
}
}
d가 1이면 ArrayIndexOutOfBoundsException 발생. d를 0으로 바꾸면 ArithmeticException 발생. 광범위한 catch (Exception e)의 주석을 해제하면 더 구체적인 핸들러를 가려 컴파일 에러 발생.
Finally 블록
finally 블록은 예외 발생 여부와 관계없이 실행되므로 정리 작업에 이상적입니다.
try {
// 위험한 연산
} catch (ExceptionType e) {
// 에러 처리
} finally {
// 정리 작업
}
예제:
class JavaException {
public static void main(String args[]) {
try {
int d = 0;
int n = 20;
int fraction = n / d;
} catch (ArithmeticException e) {
System.out.println("Caught exception: " + e);
} finally {
System.out.println("Inside the finally block");
}
}
}
이 프로그램 실행 시 예외가 던져져도 finally 블록의 메시지 출력. d를 1로 바꾸면 예외 없음; finally 블록 여전히 실행.
요약:
- 예외는 정상 프로그램 흐름을 방해하는 런타임 에러
- 에러는 컴파일 타임(문법, 의미)과 런타임(예외)으로 나뉨
- 견고한 애플리케이션은 예상되는 모든 예외를 처리해 안정성 유지
- Java의 try-catch 메커니즘이 에러 가로채기 및 복구의 표준 방식
try 블록에 예외 가능 코드, catch로 에러 처리, finally로 정리 보장