이번 글에선 우선 자바에서 Exception에 대한 기본 개념들을 알아보고 다음 글에서 어떻게하면 코드에서 잘 쓸 수 있을 지 알아보자.
프로그램 에러
- 프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우, 그 때 원인을 프로그램 에러 또는 오류라고 한다.
프로그램 에러의 종류
- 컴파일 에러(compile-time error)
- 런타임 에러(runtime error)
-
논리적 에러(logical error)
컴파일도 잘되고 실행도 잘되지만 의도와 다르게 동작
- 예: 상품 재고가 음수가 됨
실행 도중 발생하는 에러
컴파일을 정상적으로 마쳐도 프로그램 실행 시에 에러가 발생할 수 있다. 이런 런타임 에러를 방지하기 위해서는 프로그램의 실행도중 발생할 수 있는 모든 경우의 수를 고려하여 이에 대한 대비를 해야 한다.
자바에선 런타임에 발생할 수 있는 오류를 Error와 Exception 두 가지로 구분했다.
-
Error - 에러
일단 발생하면 복구할 수 없는 심각한 오류
예) OutOfMemoryError, StackOverflowError
-
Exception - 예외
발생하더라도 수습할 수 있는 정도. 프로그래머가 미리 적절한 코드를 작성해놓으면 프로그램의 비정상적인 종료를 막을 수 있음
Exception과 Error 클래스의 상속 구조
- 자바에선 실행 시 발생할 수 있는 오류(Exception과 Error)도 객체이다. 그리고 모든 클래스의 조상은 Object이므로, Exception과 Error클래스도 Obejct의 자손이다.
-
Throwable
클래스는 모든 에러와 예외의 super클래스다. Throwable을 상속한 타입이여야 자바의 throw문에서 던져질 수 있고, catch절의 인자로 전달될 수 있다. Throwable은 만들어졌을 때 쓰레드의 execution stack의 스냅샷을 갖고 있어 StackTrace 정보를 갖고 있다.- throwable을 상속해서 쓸 수도 있지만 사용하지 말자.
Error
시스템 레벨에서 발생하여 개발자가 어떻게 할 수 없는 것
-
예
OutOfMemoryError: JVM에 설정된 메모리의 한계 벗어남
- 힙 사이즈 부족, 너무 많은 class 로드, 가용가능한 swap이 없을 때
이를 해결하기 위해 dump 파일 분석, jvm 옵션 수정등을 할 수 있다.
Exception
- 모든 Exception의 최고 조상은 Exception 클래스이다.
-
예외 클래스는 또 두 개의 그룹으로 나눠질 수 있다.
RuntimeException
클래스와 자손들- RuntimeException 클래스와 자손들이 아닌,
Exception
클래스와 자손들
RuntimeException
클래스들: Unchecked Excpetion
- 주로 프로그래머의 실수에 의해서 발생 가능
-
예
- IndexOutOfBoundsException: 자바의 배열 범위 벗어남
- NullPointerException: null 참조
- ClassCastException: 변환할 수 없는 클래스로 변환
- ArithmeticException: 정수를 0으로 나눔
- IllegalArgumentException: 잘못된 인자 전달 시 발생
- IllegalStateException: 객체의 상태가 메소드 호출에는 부적절
- UnsupportedOperationException: 객체가 요청된 operation을 지원하지 않는 경우
- Unchecked: 예외처리를 필수적으로 하지 않아도 컴파일 된다
-
Error
와 그 자손들도 Unchecked 에러이다. try-catch 블럭으로 처리할 수 없기 때문이다.- 에러는 보통 JVM이 자원 부족 등 더 이상 수행을 계속할 수 없는 상황을 나타낼 때 사용한다. 따라서 Error 클래스를 상속해 하위 클래스를 만드는 일은 자제하자.
Exception
클래스들: Checked Exception
- 주로 외부의 영향, 프로그램 사용자의 동작에 의해 발생
-
예
- IOException
- FileNotFoundException
- ClassNotFoundException
- DataFormatException
- Checked: 예외 처리를 하지 않으면 컴파일 되지 않는다.
Exception Handling
- 정의: 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
- 목적: 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것
발생한 예외를 처리하지 못하면 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외(uncaugt exception)는 JVM의 예외처리기(UncaughtExceptionHandler)가 받아서 예외의 원인을 화면에 출력한다.
예외 발생 시엔 직접 try-catch문을 이용해 처리하거나, 예외를 던져서 호출하는 쪽이 책임을 지게 할 수 있다.
try-catch
- if문과 달리 try나 catch 블럭 내에 포함된 문장이 하나뿐이어도 괄호를 생략할 수 없다.
- if-else처럼 catch 문을 여러개 적을 수 있고 예외의 종류와 일치하는 catch 블럭이 있으면 예외가 처리된다.
- 예외가 발생하면 try 블럭에서 나머지는 실행되지 않는다.
-
처리 과정
- 예외가 발생하면 발생한 예외에 해당하는 클래스의 인스턴스가 만들어 진다. 그리고 예외가 발생한 문장이 try-catch문의 try블럭에 포함되어 있다면 이 예외를 처리할 수 있는 catch블럭이 있는지 찾는다.
- 첫번째 catch블럭부터 내려가면서 catch블럭의 괄호내에 선언된 참조변수의 종류와, 생성된 예외 인스턴스에
instanceof
연산자를 이용해 검사 결과가 true가 나올 때까지 반복한다. - 모든 예외는 Exception 클래스의 자손이므로 Exception 클래스 타입의 참조 변수를 선언해놓으면 어떤 예외가 발생하더라도 처리된다.
Method
try-catch 시 유용하게 쓸 수 있는 Exception 메서드들
printStackTrace()
: 예외 발생 당시의 Call Stack에 있었던 메서드의 정보와 예외 메세지 출력getMessage()
: 발생한 예외클래스의 인스턴스에 저장된 메세지 String 반환getStackTrace()
: printStackTrace를 보완하여 StackTraceElement[]라는 문자열 배열로 변경해서 출력하고 저장
멀티 catch블럭
|
기호를 이용해서 여러 catch 블럭을 하나로 합칠 수 있다. (|
는 여기서 논리 연산자가 아닌 기호이다.) 개수에는 제한이 없다.
try {
...
} catch (ExceptionA e | ExceptionB e) {
e.printStackTrace();
}
- | 기호로 연결된 예외 클래스가 조상과 자손 관계라면 컴파일 에러. 조상 클래스만 적어도 되므로 불필요한 코드라는 의미의 에러다.
예외 발생시키기 throw
throw를 사용해서 고의로 예외를 발생시킬 수 있다.
throw new Exception("메세지");
// 혹은
Exception e = new Exception("메세지");
throw e;
이 때 생성자에 String을 넣어 주면, 이 String이 Exception 인스턴스에 메세지로 저장되며, getMessage()
를 통해 얻을 수 있다.
메서드에 예외 선언 throws
예외 처리를 현재 메소드가 직접 처리하지 않고 호출한 곳에다가 예외의 발생 여부를 통보한다.
void method() throws Exception1, Exception2 {
//...
}
일반적으로 메서드에 예외 선언 시 RuntimeException들은 적지 않는다. 적어도 상관은 없지만 처리를 안해줘도 되므로 반드시 처리해주어야 하는 예외만 선언한다.
이 때 Checked Exception이 메서드의 선언부에 명시되어 있다면, 예외를 던진 계층부터 처리한 계층까지의 모든 메소드에서 시그니처를 수정하고 처리를 해줘야 한다.
메서드 내부에서 처리해도 되는 건 자체적으로 try-catch로 처리하고, 메서드의 인자가 잘못되어 다시 받아야 하는 것 같이 메서드 내에서 자체적으로 해결이 안될 때 예외를 메서드에 선언해서 호출한 메서드에서 처리하도록 할 수 있다.
- 메서드 내부와 호출한 쪽에서 모두 처리해야할 경우엔, 메서드 내부 catch블럭에서 처리 후 또 throw를 하여 호출한 쪽에서도 처리하게 할 수 있다. (exception re-throwing)
chained exception
한 예외가 다른 예외를 발생시킬 수도 있다. 예를 들어 예외 A가 예외 B를 발생시켰다면 A를 B의 원인 예외라고 한다. 이 때 원인 예외를 지정해서 다른 예외를 발생시킬 수 있다.
try {
} catch (SpaceException e) {
// 새로운 예외 생성
InstallException ie = new InstallException("설치중 예외 발생");
// 발생한 예외를 원인 예외로 지정한다.
ie.initCause(e);
throw ie;
}
이 방식은 checked예외를 unchecked 예외로 바꾸기 위해서도 사용할 수 있다.
throw new RuntimeException(new ChecekdException(".."));
위 initCause
대신에 RuntimeException의 생성자로 Throwable
타입의 원인을 넘길 수도 있다.
사용자 정의 예외
- Exception이나 RuntimeException을 상속해서 예외 클래스를 만들 수 있다. 하지만 가능하면 새로운 예외 클래스를 만들기보다 기존의 예외 클래스를 활용하자.
-
이 때 Exception을 상속해야할까 RuntimeException을 상속해야할까?
- Exception을 상속하면 Checked예외로 꼭 처리해줘야 한다.
- 다음 글에서 한번 알아보자!
다음 글
이제 기본 개념을 알아봤으니 잘 활용하는 방법을 알아보자