자바의 예외에 대해 정리해보려 한다.
예외(Exception)의 종류
예외는 크게 에러와 예외로 나눌 수 있다.
- 에러(Error)
- 예외(Exception)
- 체크 예외(Check Exception)
- 언체크 예외(UnCheck Exception)
에러(Error)
java.lang.Error이다. Error는 메모리가 부족한 경우 등과 같이 시스템이 비정상적인 상황인 경우에 사용한다.
- 시스템의 비정상적 상황에 대응
- 주로 JVM에서 발생시킴.
- 애플리케이션 코드에서 잡아서는 안되고, 잡아서 대응할 수 있는 방법도 없다.
- 따라서 에러 처리를 따로 처리할 필요 없다.
예외(Exception)
java.lang.Exception 클래스에 속하는 것들이다. catch (Exception e) 로 처리하면 모든 예외에 대해 처리할 수 있다.
하위 클래스들은 Error와 다르게 애플리케이션 코드에서 예외가 발생했을 경우 사용된다.
예외는 체크예외, 언체크예외로 구분된다.
체크 예외
체크 예외는 RuntimeException 클래스를 상속받지 않은 예외 클래스들이다.
- 복구 가능성이 있는 예외이다.
- 따라서 반드시 예외 처리 코드를 작성해야 한다.
- catch문으로 잡거나 throws를 통해 메서드 밖으로 던질 수 있다.
- 예외를 처리하지 않는다면 컴파일 에러가 발생한다.
- IOException, SQLException..
체크 예외는 개발자가 실수로 예외를 처리를 누락하지 않도록 컴파일러가 체크해준다.
하지만 개발자가 모든 체크 예외를 처리해주어야 하므로 번거롭고, 신경쓰지 않고 싶은 예외까지 처리해야 한다는 단점이 있다.
언체크 예외
RuntimeException 클래스를 상속받는 예외 클래스들이다. 런타임에 발생한다.
- 복구 가능성이 없는 예외이다.
- 컴파일러가 예외처리를 강제하지 않는다.
- Error와 마찬가지로 에러를 처리하지 않아도 된다.
- 예상치 못한 상황에서 발생하는 것이 아니므로 굳이 예외처리를 강제하지 않는다.
- NullPointerException, IllegalArgumentException ..
컴파일러가 잡아주지 않으므로 전부 처리할 필요 없지만, 개발자가 실수로 예외를 누락할 수 있다는 단점이 있다.
체크예외, 언체크예외 그리고 @Transaction
스프링이 제공하는 @Transaction에서 에러 발생 시 체크 예외는 롤백이 되지 않고, 언체크 예외는 롤백이 된다.
예외 처리
- 예외 복구
- 예외 처리 회피
- 예외 전환
예외 복구
예외 상황을 파악하고 문제를 해결해 정상 상태로 돌려놓는 것.
만약 예외로 어떤 작업의 처리가 불가능하다면 다르게 작업을 처리하도록 유도함으로써 예외를 처리하는 방법이다.
예외 처리 회피
예외 처리를 직접 처리하지 않고, 자신이 호출한 곳으로 던지는 것.
만약 해당 예외를 처리하는 것이 자신이 해야할 일이 아니라고 생각된다면, 다른 메서드에서 처리하도록 넘겨줄 때 사용한다.
예외 전환
예외 회피와 마찬가지로 예외를 복구할 수 없는 상황에 사용되며, 예외 처리 회피와 다르게 적절한 예외로 변환하여 던진다는 특징이 있다.
- 의미 있고 추상화된 예외로 바꾸는 경우
- 런타임 예외로 포장하여 불필요한 처리를 줄여주는 경우
주로 이 2가지 목적으로 사용된다.
내부에서 발생한 예외를 그대로 던지는 것이 적절한 의미를 부여하지 못한다면 의미 있고 추상화된 예외로 바꾸는 것이 좋다.
또한 체크 예외에 의해 불필요하게 해주는 에러 처리가 많아진다면 이를 해결하기 위해 런타임 예외로 포장(언체크 예외로 변경)하여 불필요한 처리를 줄일 수 있다.
예외 처리 방법
프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성해 강제 종료 등을 막고 정상적인 실행을 유도하도록 처리해야 한다.
try - catch 문
1
2
3
4
5
6
7
try {
//예외가 발생할 가능성이 있는 로직
} catch (Exception1 e1) {
//Exception1이 발생했을 경우 이를 처리할 수 있는 코드를 삽입
} catch (Exception2 e2) {
//Exception2가 발생했을 경우 이를 처리할 수 있는 코드를 삽입
}
위와 같은 흐름으로 진행한다.
하나의 try 블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch 블럭이 올 수 있다. 이 중 발생한 예외의 종류와 일치하는 단 한개의 catch 블럭만 수행된다. 일치하는 catch블럭이 없다면 예외는 처리되지 않는다.
1
2
3
4
5
6
7
8
try {
System.out.println(1);
System.out.println(1/0); //예외 발생
System.out.println(4); //수행 x
} catch (ArithmeticException ae) {
System.out.println(5);
}
System.out.println(7);
1
2
3
1 //try
5 //catch
7 //try-catch 밖
0으로 나누는 시도에 ArithmeticException이 발생한다. try 구문 중 오류가 발생하면 그 이후의 코드는 수행되지 않고, catch문을 수행한 뒤 try-catch문을 빠져나간다.
printStackTrace(), getMessage()
1
2
3
4
catch (ArithmeticException ae) {
ae.printStackTrace();
log.info("exception message={}", ae.getMessage());
}
- printStackTrace(): 예외 발생 당시의 호출 스택에 있던 메서드의 정보와 예외 메시지를 화면에 출력한다.
- java.lang.ArithmeticException: at ExceptionEx8.main(ExceptionEx8.java:7)
- getMessage(): 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
- throw new Exception(“예외 발생”) 으로 던져진 예외를 받으면 “예외 발생” 문구를 출력한다.
예외 발생시키기
프로그래머가 고의로 예외를 발생시킬 수 있다.
- 연산자 new를 이용해 발생시키려는 예외 클래스의 객체를 만든다.
- Exception e = new Exception(“고의 발생”)
- 키워드 throw를 이용해 예외를 발생시킨다.
- throw e;
- 둘을 합쳐도 된다.
- throw new IllegalStateException(“상태 예외 발생”)
메서드에 예외 선언하기
1
2
void method() throws Exception1, Exception2,,, {
}
위와 같이 메서드 선언부에 throws 키워드를 이용해 선언할 수 있다.
메서드 내에서 발생할 수 있는 예외를 선언하기 위해 사용한다. 예외가 처리되는 것은 아니고 단순히 전달하는 역할이다.
메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 이 메서드를 사용하기 위해 어떤 예외들이 처리되어야 하는지 쉽게 알 수 있다.
컴파일러가 체크 예외들을 이렇게 메서드에 선언하도록 강제한다. 따라서 보다 견고한 프로그램 코드를 작성할 수 있도록 도와준다.
throws
throws는 예외를 위임한다고 보면 된다.
예외를 전달받은 메서드가 또 다시 자신을 호출한 메서드에 전달할 수 있으며 이런식으로 계속 호출 스택에 있는 메서드들을 따라 전달되다가 제일 마지막에 있는 main 메서드에서도 예외가 처리되지 않으면 main 메서드마저 종료되어 프로그램 전체가 종료된다.
finally
try - catch 문에 덧붙여 사용할 수 있고 try - catch - finally의 순으로 실행된다. 예외가 발생하지 않으면 try - finally 순서로 실행된다.
예외 발생 여부에 상관없이 최종적으로 반드시 실행되어야 하는 로직이 있다면 finally 블럭에 넣어주면 된다. try에 return문이 있어도 finally를 실행한 뒤 return 된다.
사용자 정의 예외 만들기
1
2
3
4
5
6
7
8
class MyException extends Exception {
private final int ERR_CODE;
MyException(String msg, int errorCode) {
super(msg); //조상클래스의 생성자를 호출
ERR_CODE = errorCode;
}
}