자바에서는 실행 시 발생할 수 있는 오류(Exception과 Error)를 클래스로 정의하였다. 앞서 배운 것처럼 모든 클래스의 조상은 Object이기에 Exception,Error모두 자손 클래스들이다.
Object - Throwable - Exception,Error 이다.
모든 예외 클래스의 최고 조상은 Exception클래스이다.
예외 클래스들은 다음과 같이 두 그룹으로 나눠질 수 있다.
- Exception클래스와 그 자손들
- RuntimeException클래스와 그 자손들
물론 Exception이 포괄적으로 RuntimeException보다 상위 계층이지만, 예외에서는 Exception과
RuntimeException을 구분하는 편이다.
Exception 클래스들
사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
RuntimeException 클래스들
프로그래머의 실수로 발생하는 예외
RuntimeException클래스들은 주로 프로그래머의 실수에 의해 일어난다. 보통 자바의 프로그래밍 요소들과 관계가 깊은데, 예를 들자면 배열의 범위를 벗어난다건(ArrayIndexOutOfBoundsException), 값이 null인 참조변수의 멤버를 호출하려 했다던가(NullPointException), 클래스간의 형변환을 잘못했다던가(ClassCastException), 정수를 0으로 나누려고(ZeroDivisionException, ArithmeticException)하는 경우에 발생한다.
Exception클래스들은 주로 외부의 영향으로 발생할 수 있는 것들로서, 프로그램의 사용자들의 동작에 의해서 발생하는 경우가 많다. 예를 들자면, 존재하지 않는 파일의 이름을 입력헀다던가(FileNotFoundException), 실수로 클래스 이름을 잘못 적었던가(ClassNotFoundException), 또는 입력한 데이터 형식이 잘못된(DataFormatException)경우에 발생한다.
Exception / RuntimeException에 따른 DB 트랜잭션 롤백 여부 차이가 있던데 이는 자바 문법에서가 아니라 Spring 프레임워크에서의 기본값을 말하는 것이다.
예외처리(exception handling)이란, 프로그램 실행 시 발생할 수 있는 예상할 수 있는 예외의 발생에 대비한 코드를 작성하는 것이며, 이의 목적은 사소한 예외의 발생으로 인한 프로그램 비정상적 종료를 막기위함이다.
예외처리(exception handling)의
- 정의 : 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
- 목적 : 프로그램의 비정상 종료를 막고, 정상적인 실행 상태를 유지하는 것
발생한 예외를 처리하지 못하게 되면, 프로그램이 종료됨과 동시에 처리되지 못한 예외는 JVM의 '예외 처리기(UncaughtExceptionHandler)'가 받아서 원인을 화면에 출력한다.
예외를 처리하기 위해서는 try-catch문을 사용하는데 그 구조는 아래와 같다.
try {
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (ExceptionType1 e1) {
// ExceptionType1클래스의 예외가 발생한 경우, 이를 처리할 코드 작성
} catch (ExceptionType2 e2) {
// ExceptionType2클래스의 예외가 발생한 경우, 이를 처리할 코드 작성
} catch (ExceptionType3 e3) {
// ExceptionType3클래스의 예외가 발생한 경우, 이를 처리할 코드 작성
}
if문과 달리, try나 catch의 블럭안에 들어갈 문장이 하나여도 {}를 생략할 수 없다.
try블럭 내에서 예외가 발생한 경우,
1. 발생한 예외와 일치하는 catch블럭이 있는지 확인한다.
2. 일치하는 catch블럭을 찾게 되면, 그 catch블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져 나가서 그 다음 문장을 계속해서 수행한다.
2-1. 일치하는 catch블럭을 찾지 못하면 예외는 처리되지 못한다.
catch블럭은 괄호()와 블럭{} 두 부분으로 나누어져 있는데, 괄호()내에는 처리하고자 하는 예외와 같은 타입의 참조변수 하나를 선언해야 한다.
예외가 발생하면, 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다. 위 try-catch 예제문에서는 ExceptionType1예외가 발생하면 e1에 ExceptionType1클래스의 인스턴스가 만들어 지는것이다.
첫 번째 catch블럭부터 차례로 내려가면서 catch블럭의 괄호()내에 선언된 참조변수의 종류와 생성된 예외클래스의 인스턴스에 instanceof연산자를 이용해서 검사하고 true가 나올때까지 catch블럭을 검사한다.
모든 예외 클래스는 Exception을 상속받으므로 catch(Exception e)처럼 넣어주게 되면 해당 catch문은 모든 예외를 처리하는 catch블럭으로 만들 수 있다.
따라서 내가 예측할 수 있는 예외종류들은 명확히 catch()안에 해당 예외클래스를 참조변수의 클래스타입으로 지정하여 사용할 수 있고 내가 예측할 수 없는 예외종류들은 catch(Exception e)처럼 한꺼번에 처리 할 수있다.
try {
System.out.println(0/0) // ArithmeticException
} catch(ArithmeticException ae) {
// Arithmetic예외가 발생했을 때 처리 할 코드
} catch(Exception e) {
// 나머지 예상 불가능한 예외가 발생했을 때 처리 할 코드
}
예외가 발생했을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨 있으며, getMessage()와 printStackTrace()를 통해서 이 정보들을 얻을 수 있다.
printStackTrace()
예외발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메세지를 화면에 출력한다.
getMessage()
발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
java7 이상부터는 여러 catch블럭을 '|'을 이용해 하나의 catch블럭으로 합칠 수 있게 됐다.
'|'기회(논리 연산자 아님)로 연결할 수 있는 예외 클래스의 개수에는 제한이 없다.
try {
...
} catch (ExceptionA e) {
e.printStackTrace();
} catch (ExceptionB e2) {
e2.printStackTrace();
}
// 를 아래와 같이 바꿀 수 있다.
try {
...
} catch (ExceptionA | ExceptionB e) {
e.printStackTrace();
}
하지만 유의해야 할 점이 있다. 바로 '|'로 묶은 예외 클래스들이 서로 상속관계이면 안된다는 점인데 아래 try-catch문은 서로 상속관계라 오류가 난다.
try { ... }
catch (ParentException | ChildException e) { ... }
사실 이런 오류는 발생할 필요가 없다. 그냥 부모 예외 클래스만 적어주면 되기 때문이다(다형성).
그리고 멀티 catch는 하나의 catch블럭으로 여러 예외를 처리하는 것이라 발생한 예외를 멀티 catch로 처리하게 됐을 때, 멀티 catch블럭 내에서는 실제로 어떤 예외가 발생한 것인지 알 수 없다. 그래서 참조변수 e로 멀티 catch블럭에 '|'기호로 연결된 클래스들의 공통 분모인 조상 예외 클래스에 선언된 멤버만 사용할 수 있다.
try {
...
} catch (ExceptionA | ExceptionB e) {
e.methodA(); // 에러. ExceptionA에 선언된 methodA()는 호출 불가
if(e instanceof ExceptionA) {
ExceptionA e1 = (ExceptionA)e;
e1.methodA(); // OK. ExceptionA에 선언된 메서드 호출가능
} else { ... }
}
위와같이 멀티 catch문에서도 if문에 따라 형변환을 통해 메서드 호출은 가능하지만 이럴거면 그냥 멀티 catch문을 사용 안하는게 좋을 것 같다.
키워드 'throw'는 고의로 예외를 발생시키는 키워드이다.
- 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
Exception e = new Exception("고의로 발생시킴");- 키워드 throw를 이용해서 예외를 발생시킨다.
throw e;
finally블럭은 예외의 발생여부와 상관없이 try-catch문 실행 후에 무조건 실행되는 코드뭉치들이다.
try {
} catch (Exception e) {
} finally {
// 예외가 발생하든, 발생하지 않든 무조건 실행해야할 코드 작성
}