[9주차] 예외처리
목차
- 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
- 자바가 제공하는 예외 계층 구조
- Exception과 Error의 차이는?
- RuntimeException과 RE가 아닌 것의 차이는?
- 커스텀한 예외 만드는 방법
1. 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
1.1 예외 복구 : try ~ catch (~ finally)
-
예외가 발생하면 JVM은 즉시 프로그램을 중단하고, 해당 예외가 등록된 클래스를 객체로 만들어 Throw한다.
-
이 때, JVM이 throw한 예외를 잡는 것이 try ~ catch구문이다.
try {
// 예외가 발생할 가능성이 있는 코드
} catch (Exception1 e) {
// Exception1이 발생했을 때 처리하는 코드
} finally {
// 예외가 발생하던 안하던 무조건 실행되는 코드
}
-
예외가 발생한 경우와 발생하지 않은 경우 처리 흐름이 다르다.
-
예외가 발생하면 JVM이 즉시 프로그램 진행을 멈추기 때문이다.
1.2 예외처리 회피
-
예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것이다.
-
보통 throws문으로 선언하거나 catch문에서 rethrow한다.
1.3 예외 전환
-
예외 상황에 대해 의미가 좀 더 명확한 예외로 재정의하여 throws한다.
-
보통 전환하려는 예와에 원래의 예외를 담는 중첩 예외를 만들어서 예외를 던지는 것이 좋다.
1.4 throw와 throws
-
throw와 throws는 모두 예외를 던지는 것이다.
-
다만 throw는 Exception을 발생시킬 때 사용하는 키워드이며, 최상위 예외 클래스인 Exeption에 대해서 예외처리를 하면 거의 모든 예외가 잡힌다.
-
throws는 메소드를 정의할 때 사용하며, 메소드에서 발생하는 Exception의 종류를 명시적으로 정의할 때 주로 사용한다.
1.5 finally에서의 return
-
finally에서 return값을 던지는 것은 안티패턴이다.
-
만일 try, catch 문에 return이 있어도 finally문에 return 이 있다면 finally의 return만 수행될 수 있기 때문이다.
1.6 멀티 Catch 블록
- 아래와 같이 오류를 처리한다면 예외처리 안에서 중복 코드들이 발생하는 문제가 있다.
// JDK 1.7 이전
try {
} catch (Exception1 e1) {
// 예외처리1
} catch (Exception2 e2) {
// 예외처리2
.
.
.
} catch (ExceptionN eN) {
// 예외처리n
}
- 이러한 문제를 해결하기 위해 JDK 1.7부터는 멀티캐치문(
|
)을 제공하고 있다.- 이때, 다형성에 의해 부모 예외로 자손 예외를 처리할 수 있기 때문에 부모-자식 관계의 예외 클래스는 멀티캐치 블록을 사용할 수 없다.
// JDK 1.7이상
try{
}catch(Exception1 e1 | Exception2 e2| ExceptionN eN){
//공통 예외로직 ex)e.printStackTrace();
}
1.7 try ~ with ~ resources
-
입출력을 수행하는 도중 예외가 발생하면 입출력을 수행하던 stream이 open되어진 상태로 남겨지고, close되지 않은 스트림이 쌓여 자원부족으로 프로그램이 멈추게된
-
메모리가 점점 고갈되는 현상을
메모리 누수(Memory Leak)
이라고 한다. -
이러한 예외를 처리하기 위해 도입된 것이
try ~ with ~ resources
문이다. -
만일, 기존 방식으로 예외처리를 하게 되면 아래와 같이 소스가 복잡해지기 때문에 JDK 1.7부터 생긴 기능이다.
FileOutputStream out = null;
try {
out = new FileOutputStream("exFile.txt"); //...이후 입출력 로직 처리...
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if(out != null) {
//스트림이 null인지 체크
try {
out.close(); //close 하다가 예외가 발생할 수 있다.
}
catch (IOException e) {
e.printStackTrace();
}
}
}
-
try with resources를 사용하게 되면 예외가 발생 시, 컴파일 시 자동으로 catch문장에서
close()
를 호출 후 이후 작업이 수행되거나 예외가 없으면 그냥 바로 close가 호출된다. -
만약 finally에서 직접 close를 호출하는 코드를 사용하고 있다면 try with resorce로 변경해주는 것을 추천한다.
try(FileOutputStream out = new FileOutputStream("exFile.txt")) {
//...이후 입출력 로직 처리...
} catch(IOException e){
e.printStackTrace();
}
-
close()
메서드 호출 후, 예외를 반납하던 중에 예외가 발생하면 CloseException이다. -
try with resouces를 통해 자동으로 자원을 반납하기 위해서는 AutoCloseable 인터페이스를 구현해야 한다.
public interface AutoCloseable {
void close() throws Exception; // 예외발생 시, JVM이 close()메소드를 호출한다
}
2. 자바가 제공하는 예외 계층 구조
-
Throwable은 모든 예외의 조상이 되는 Exception클래스와 모든 오류의 조상이 되는 Error 클래스의 부모 클래스이다.
- Throwable에는 아래 메소드들이 포함되어 있다.
-
void printStackTrace()
: throwable 객체와 Standard Error Stream에서 해당 객체의 Stack Trace를 출력한다. 즉, 예외가 발생하게 되면 Stack 메모리 영역에 저장된 Stack Frame을 JVM이 Pop하면서 Stack Trace를 출력한다. -
String getMessage()
: throwable 객체에 대한 자세한 내용을 문자열로 반환한다. -
String toString()
: 해당 throwable 객체에 대한 간략한 내용을 문자열로 반환한다.
-
- Exception은 크게 RuntimeException와 Execption로 나뉜다.
2.1 RuntimeException과 기타 Execption
2.1.1 RuntimeException
-
RuntimeException은 Java Virtual Machine의 정상적인 작동 중에 발생할 수 있는 예외의 슈퍼 클래스이다.
-
RuntimeException 및 해당 서브 클래스는 unchecked exception이다.
-
unchecked exception은 메서드 또는 생성자의 실행에 의해 발생한다.
-
명시적 예외처리를 꼭 할 필요는 없다.
-
RuntimeException 클래스
-
ArrayIndexOutOfBoundException: 배열에서 존재하지 않는 인덱스 가리킬때
-
ArithmeticException: 정수를 0으로 나누려고 하는 경우
-
ClassCastException: 클래스간의 형변환을 잘못한 경우
-
NumberFormatException: 숫자데이터 문제데이터 등을 넣었을 때
-
NullPointerException: null 객체의 레퍼런스를 이용하거나 null 객체의 인스턴스 메소드를 호출하는 등
-
2.1.2 기타 Execption
- 기타 Execption은 대부분 checked exception이다.
-
checked exception은 반드시 예외 처리를 해야 한다. (try~catch를 통해)
-
checked exception은 컴파일 단계에서 확인 가능하다.
-
RuntimeException을 제외한 Exception의 하위 클래스이다.
- Execption클래스
-
ClassNotFoundException: 실수로 클래스의 이름을 잘못 적은 경우
-
DataFormatException: 입력한 데이터 형식이 잘못된 경우
-
FileNotFoundException: 존재하지 않는 파일의 이름 입력 시
-
3. Exception과 Error의 차이는?
3.1 Error
-
Error는 주로 JVM이나 하드웨어 등 기반 시스템의 문제로 발생한다.
-
발생하는 순간 프로그램이 비정상적으로 종료되며, 개발자가 Error를 처리할 수 없다.
3.1.1 컴파일에러
-
컴파일 과정에서 일어나는 에러
-
자바 컴파일러가 오류를 검사해서 알려준다.
-
하지만 컴파일 오류를 수정해도 runtime에러가 발생하는 경우가 있다.
3.1.2 런타임에러
-
실행 과정에서 일어나는 에러
-
대개 런타임에러는 Exception과 Error 두 가지로 나뉜다.
3.2 Exception
- Exception은 프로그래머가 미리 발생할 것을 예측하고 방지할 수 있다.
4. 커스텀한 예외 만드는 방법
-
커스텀한 예외처리는 throw에 직접 작성한 클래스 파일을 지정하는 것이다.
-
커스텀한 예외를 만드는 것보다 IllegalArgumentException 이나 UnsupportedOperationException을 사용하는 것이 더 나을때가 많다.
-
정말 꼭 필요한건지 커스텀한 예외를 만들기 전에 꼭 고려해보자
-
커스텀 예외를 던질때 catch문에서 꼭
5. 연결된 예외(Exception chaining), Java8
5.1 Exception chaining이란?
-
한 예외가 다른 예외를 발생시킬 수 있다.
-
예외 A가 예외 B를 발생시키면, A는 B의 원인 예외(Cause Exception)이다.
-
이때 A와 B를 연결시키는 것을 연결된 예외라고 한다.
-
예외 안에 또 다른 예외를 포함시키는 것이 연결된 예외라고 볼 수 있다.
-
Throwable 클래스의 생성자가 이런 연쇄적 예외 기능을 지원한다.
Method | Description |
---|---|
Throwable getCause() | Returns the original cause of the exception |
Throwable initCause(Throwable cause) | Sets the cause for invoking the exception |
public class Example {
public static void main(String[] args) {
try {
// 새로운 예외 객체 e를 생성한다.
ArithmeticException e = new ArithmeticException("Apparent cause");
// 지정된 예외를 원인 예외로 등록한다.
e.initCause(new NullPointerException("Actual cause"));
// 예외를 던진다.
throw e;
} catch(ArithmeticException e) {
// 예외의 지정된 원인 예외를 반환해서 출력한다.
System.out.println(e.getCause());
// 출력결과 : java.lang.NullPointerException: Actual cause
}
}
}
5.2 연결된 예외를 사용하는 이유
- 여러 예외를 하나로 묶어서 다루기 위해서이다.
- 이는 예외에 대한 좀 더 상세한 설명을 예외 시 보여주고 싶을 때 유용하다.
void install() throws InstallException {
try {
startInstall(); // SpaceExeption 발생
copyFiles();
} catch (SpaceException e) {
InstallException ie = new InstallException("설치중 예외발생"); // 예외 생성
ie.initCause(e); // InstallException의 원인 예외를 SpaceException으로 지정
throw ie;
} catch (MemoryExeption me) {
/* ...... */
}
}
- checked 예외를 unchecked예외로 변경하려할 때 사용한다.
- 즉, 필수처리 예외를 선택처리 예외로 바꾸고 싶을 때 사용한다.
- RuntimeException을 만들고 그 안에 원인 예외로 등록하면 된다.
- 이 때는
initCause
메소드가 아니라생성자
를 사용한다.
static void startInstall() throws SpaceExeption {
if(!enoughSpace())
throw new Exception("설치할 공간이 부족합니다");
if(!enoughMemory())
throw new RuntimeException(new MemoryExeption("메모리가 부족합니다"));
}