본문 바로가기
JAVA

JAVA 예외 처리

by mjjang 2021. 1. 15.

목표

자바의 예외 처리에 대해 학습하세요.

학습할 것 (필수)

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법

1. 자바가 제공하는 예외 계층 구조

Throwable 클래스 : 모든 예외의 조상이 되는 Exception 클래스와 모든 오류의 조상이 되는 Error 클래스의 부모 클래스

Exception과 Error는 Throwable 클래스를 상속받고 있습니다.

2. Exception과 Error의 차이는?

오류(Error)는 시스템에 비정상적인 상황이 생겼을 때 발생합니다. 프로그램 레벨에서 대비할 수 없거나 복구 불가능한 치명적인 상황을 말합니다. 자바에서 오류는 메모리가 가득 차서 발생하는 Out Of Memory, 스택이 가득 차서 발생하는 Stack Over flow가 있습니다.

예외(Exception)는 개발자가 잘못된 구현을 했거나 사용자의 잘못된 조작으로 발생합니다. 즉, 예외는 어느 정도 예측을 해서 처리할 수 있다는 것입니다. 예를 들어 사용자가 올바르지 않은 값을 입력할 때마다 프로그램이 종료된다면 아무도 그 프로그램을 사용하지 않을 것입니다. 그래서 개발자들은 예외가 일어날 상황을 잘 판단하여 예외처리를 해야 합니다.

3. RuntimeException과 RE가 아닌 것의 차이는?

예외의 종류에는 Checked Exception과 UnChecked Exception이 있습니다.

Checked Exception이란 반드시 예외처리를 해야 하는 예외입니다. 예외처리를 하지 않으면 컴파일 시점에서 예외처리를 하라는 에러가 발생합니다.

UnChecked Exception은 반드시 예외처리를 하지 않아도 되는 런타임에 발생하는 예외입니다. 그래서 RuntimeException이라고 부릅니다. 예외처리를 강제하지 않기 때문에 개발자 스스로의 판단으로 예외처리 코드를 작성해야 합니다.

4. 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

try - catch 문

자바에서 예외처리를 하는 가장 기본적인 방법입니다.

try {
  // 예외가 발생할 가능성이 있는 명령
} catch (Exception ex) {
  // 예외가 발생했을 경우 처리하기 위한 명령
}

하나 이상의 catch문이 올 수 있고, 이 중 발생한 예외의 종류와 일치하는 단 하나의 catch 블럭만 수행됩니다. 발생한 예외의 종류와 일치하는 catch 블럭이 없을 경우 예외는 처리되지 않습니다.

multi catch 블럭

try {
    // code
} catch (ExceptionA | ExceptionB e) {
    // code
} 

JDK 1.7부터 여러 catch 블럭을 하나의 catch 블럭으로 합칠 수 있게 되었고 이를 multi catch 블럭이라고 합니다.
'|' 기호로 여러 예외들을 하나의 catch 블럭 안에서 사용할 수 있는데 각 예외들(ExceptionA와 ExceptionB)은 부모 자식 관계가 아니어야 합니다.

throw

throw 키워드를 이용해서 개발자가 직접 예외를 발생시킬 수 있습니다.

public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            if (i == 5) throw new RuntimeException("5입니다.");
        }
    }

throws

throws 키워드는 예외를 메소드에 선언해서 메소드를 호출한 쪽에서 예외를 처리하게 하는 방법입니다.

public static void main(String[] args) {
        try {
            readData();
        } catch (IOException e) {
            System.out.println("예외 처리");
        }
    }

    private static void readData() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        br.readLine();
    }

readData() 메소드는 IO에 접근하기 때문에 예외처리를 필수적으로 해야 합니다. 그럴 때 try - catch 문으로 예외처리를 하는 방법이 있고 throws 키워드를 이용해서 readData() 메소드를 호출한 메소드에게 예외를 넘길 수 있습니다. 위에 예시를 보시면 readData() 메소드에서 넘긴 예외를 main() 메소드에서 처리하는 것을 볼 수 있습니다.

finally

finally 블럭은 try - catch 문과 함께 예외의 발생 여부에 상관없이 실행되어야 할 코드를 포함시킬 목적으로 사용합니다.

    public static void main(String[] args) {
        try {
            System.out.println("try");
            int num = 0 / 0;
            return;
        } catch (Exception e) {
            System.out.println("exception catch");
            return;
        } finally {
            System.out.println("finally");
        }
    }

finally 블럭은 try 블럭과 catch 블럭에 return 문이 있어도 항상 실행됩니다.

try-with-resources

JDK 1.7부터 등장한 try-with-resources는 IO 관련된 클래스에서 자원을 사용한 후 반환할 때 주로 사용합니다.

    public static void main(String[] args) {
        InputStream is = null;
        try {
            is = new FileInputStream("fileName");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

try-with-resources 구문을 사용하지 않았을 때는 InputStream의 자원을 해제하기 위해 finally 블럭에 자원을 해제하는 close() 메소드를 사용했습니다. 하지만 close() 메소드에도 예외 처리를 해줘야 하기 때문에 try-catch 구문으로 감싸줘야 해서 코드가 상당히 복잡해 보이고 읽기 힘들어졌습니다.

public static void main(String[] args) {
        try (InputStream is = new FileInputStream("fileName")) {

        } catch (IOException e) {
            e.printStackTrace();
        } 
    }

try-with-resources 구문을 사용하면 try() 괄호 안에 해제시킬 자원을 명시하면 따로 해제를 시켜주지 않아도 try 블럭을 빠져나갔을 때 자동으로 자원이 해제됩니다. try() 괄호 안에는 AutoCloseable 인터페이스를 구현한 클래스만 사용할 수 있습니다.

5. 커스텀한 예외 만드는 방법

기존에 정의된 예외 클래스 외에 필요에 따라 새로운 예외 클래스를 정의하여 사용할 수 있습니다. 보통 Exception 클래스를 상속받는 클래스를 만들지만, 필요에 따라 알맞은 예외 클래스를 상속받을 수 있습니다.

public class MyException extends RuntimeException {
    private final int ERROR_CODE;

    MyException(String msg, Throwable cause, int errorCode) {
        super(msg, cause);
        ERROR_CODE = errorCode;
    }

    MyException(String msg, Throwable cause) {
        this(msg, cause, 100);
    }

    public int getErrorCode() {
        return ERROR_CODE;
    }
}

커스텀 예외 클래스는 RuntimeException을 상속받았습니다. 그리고 캐치된 예외를 super 클래스에게 전달하기 위해 커스텀한 클래스의 생성자에서 super생성자에 파라미터로 캐치된 예외 인스턴스를 넘겼습니다.

커스텀 예외 best practice

  1. Java 표준 예외를 사용하는 것보다 커스텀 예외를 사용하는 게 더 많은 이익을 얻는다고 생각할 경우에만 커스텀 예외를 구현하자.
  2. 예외 클래스의 이름의 끝은 Exception으로 끝나도록 하자.
  3. API 메소드가 어떤 하나의 예외를 기술하고 있다면, 그 예외는 API의 한 부분이므로 꼭 예외를 문서화 하자.
  4. 예외의 cause를 설정할 수 있는 생성자를 제공하자.

참고

https://pridiot.tistory.com/54
https://m.blog.naver.com/sthwin/221144722072
https://itmining.tistory.com/9

'JAVA' 카테고리의 다른 글

자바 쓰레드(Thread)  (0) 2021.01.25
Garbage Collection  (0) 2021.01.08
자바 인터페이스  (0) 2021.01.07
자바 패키지에 관하여  (0) 2020.12.31
JVM 구조와 JAVA 메모리 구조  (0) 2020.12.28

댓글