프로그램 실행 중 오류에는 여러가지 종류가 있다.
자바에서는 이 오류들을 에러(Error)와 예외(Exception)로 구분한다.
프로그래머는 예외 처리 코드를 작성함으로써 프로그램에 예외가 발생했을 때 이를 처리하여 비정상적인 프로그램 종류를 방지할 수 있다.
모든 예외의 조상은 Exception 클래스이다.
예외 클래스들은 다음과 같이 두개의 그룹으로 나누어 질 수 있다.
A. RuntimeException 클래스와 그 자손 클래스들
B. A를 제외한 Exception 클래스와 그 자손 클래스들
A (RuntimeException 클래스) 는 주로 프로그래머의 실수에 의해서 발생될 수 있는 예외들이다.
ex) 인덱스 배열 범위 벗어남, null인 참조변수의 멤버 호출, 클래스 형변환 잘못함, 정수를 0으로 나눔 등
B (Exception 클래스) 는 외부의 영향으로 발생할 수 있는 예외들로서, 프로그램을 사용하는 사용자들의 동작에 의해서 발생하는 경우가 많다.
ex) 파일 제목 잘못 입력, 클래스 이름 실수, 입력 데이터 형식 오류 등
예외 처리란, 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하여 프로그램의 비정상 종료를 방지하고 정상적인 실행상태를 유지할 수록 있는 것이다.
이를 위해 try-catch 문을 사용하고 구조는 다음과 같다.
try {
// 예외 발생 가능성 있는 문장을 작성한다.
} catch (Exception1 e1) {
// Exception1이 발생했을 경우 처리하는 문장을 작성한다.
} catch (Exception2 e2) {
// Exception2이 발생했을 경우 처리하는 문장을 작성한다.
} ...
하나의 try 블럭 다음에는 여러 종류의 예외를 처리할 수 있는 여러 catch 블럭이 올 수 있다. 이 중에 예외와 일치하는 단 하나의 catch 블럭만 수행딘다. 일치하는 예외가 없으면 예외는 처리되지 않는다.
try 또는 catch 블럭 내에서도 예외가 발생할 수 있기 떄문에 try 또는 catch 블럭 안에도 try 문을 적어서 중첩할 수 있다.
public static void main(String[] args) {
int number = 100;
int result;
for(int i = 0; i < 10; i++) {
try {
result = number / (int) (Math.random() * 10);
System.out.println(result);
} catch (Exception e) {
System.out.println("0 으로 나누는 예외 발생");
}
}
}
위 코드에서는 예외가 발생한다면 ArithmeticException 예외가 발생하는데, 이것은 Exception 클래스의 자손이므로 instanceof 연산결과가 true가 되어 Exception e 에서 예외가 잡혀서 catch 블럭이 수행된다.
예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨져 있다.
이를 얻을 수 있는 메소드는 다음과 같다.
이 두 메소드는 catch 블럭 내에서만 사용 가능하다.
...
catch (ExceptionA | ExceptionB e) {
e.printStackTrace();
System.out.println("예외메세지: " + e.getMessage());
}
참고: |
기호를 사용하여 여러 예외를 하나의 catch블럭으로 다룰 수 있다.
키워드 throw
를 사용해서 고의로 예외를 발생시킬 수 있다.
throw new Exception();
이 때, Exception 생성자에 String을 넣으면, 그것이 Exception 인스턴스에 메세지로 저장된다. -> getMessage로 얻을 수 있다.
위에서 설명한 A 그룹 (RuntimeException 클래스와 그 자손 클래스들) 을 고의로 발생시키는 코드가 포함되어도 컴파일이 정상적으로 작동한다.
하지만 B 그룹 (A를 제외한 Exception 클래스와 그 자손 클래스들) 은 고의로 발생하고 예외 처리 (try-catch)를 해주지 않으면 컴파일 자체가 안된다.
A 그룹은 프로그래머의 실수로 발생하는 것들이기 때문에 예외처리를 강제하지는 않는 것이다.
그래서 A 그룹은 unchecked 예외 라고 부르고,
예외처리를 확인하는 B그룹은 checked 예외 라고 부른다.
void method() throws Exception1, Exception2 {
// 메서드 내용
}
다음과 같이 메서드에 예외를 선언할 수 있는데, 이것은 이 메소드가 해당 예외들을 발생시킬 수도 있다고 표시해주는 것이다.
일반적으로 RuntimeException (unchecked 예외)는 꼭 처리할 필요는 없으므로 메소드 선언부에 작성하지 않는다.
해당 메소드에서 예외를 처리하지 않고, 자신을 호출한 메소드에서 예외 처리를 떠넘겨서 맡길 때 작성하면 된다. (예외 전달)
예외가 발생한 메서드에서 바로 예외 처리
public void a() {
method1();
}
static void method1() {
try {
throw new Exception();
} catch (Exception e) {
System.out.println("method1 에서 예외 처리됨.");
e.printStackTrace();
}
}
상위 메서드에서 예외 처리
public void a() {
try {
method1();
} catch (Exception e) {
System.out.println("main 에서 예외 처리됨.");
e.printStackTrace();
}
}
static void method1() throws Exception {
throw new Exception();
}
이 두 가지의 차이를 보자. 1번에서는 예외가 발생해도 method1 내에서 처리됐기 때문에 a 메소드는 예외가 발생했다는 사실도차도 모르게 된다.
하지만 2번에서는 예외가 발생한 메소드에서 예외를 처리하지 않고 호출한 메서드로 넘겨줬기 때문에 a 메소드는 method1()이 호출된 부분에서 예외가 발생했다고 판단한다.
이처럼 예외 처리 부분을 결정할 수 있다.
finally 블럭은 try-catch 문과 함께 마지막 순서로 선택적으로 쓰인다.
예외 발생 여부와 상관없이 실행된다.
try {
// 예외 발생 가능성 문장
} catch (Exception1 e) {
// 예외 처리 문장
} finally {
// 예외 발생 여부와 상관없이 실행할 문장
}
try문과 catch 문에 중복으로 작성해야할 코드가 생겼을때 해당 코드를 finally 블럭 안으로 옮겨주면 좋다.
참고: try 또는 catch 블럭에서 return 문이 실행되어도 finally 블럭의 문장이 먼저 실행된 다음 return된다.