[STUDY HALLE] 9주차 과제: 예외처리

3 minute read

학습 목표

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

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

Deadline: ~2021/01/16 13:00

Exception과 Error의 차이

에러(Error)는 프로그램이 비정상적으로 종료되거나 오작동하는 것이다. 에러는 컴파일 에러와 런타임 에러로 구분되는데, 컴파일 에러는 자바 컴파일러가 문법 검사를 하는 과정에서 발견되는 에러이다. 하지만 컴파일이 정상적으로 되더라도 프로그램 실행 중 에러가 발생할 수 있고, 이것이 런타임 에러다. 런타임 에러는 다시 예외(Exception)와 에러(Error)로 나누어진다. 사실 에러의 하위 개념이 런타임 에러이고, 런타임 에러의 하위 개념이 에러라는 점이 굉장히 헷갈렸다. 용어가 중복되는데(심지어 하위 개념으로), 용어일 뿐 이므로 그대로 받아들이기로 했다.
에러는 시스템 레벨에서의 심각한 오류를 의미한다. OutOfMemory, StackOverFlow와 같이 발생하는 순간 프로그램이 비정상 종료되고, 프로그래머가 대비할 수 없는 오류들이다.
예외는 발생하더라도 적절한 코드를 작성해두어 처리할 수 있는 오류이다.

예외 계층 구조

예외계층구조

  Checked Exception Unchecked Exception
처리여부 반드시 처리해야함 강제 X
확인시점 컴파일 단계 실행 단계
예외 발생 시 트랜잭션 roll back X 트랜잭션 roll back

반드시 처리해야하는 예외와 그렇지 않은 예외를 왜 굳이 나누었을까? 우선 메소드를 호출하는 쪽에서는 메소드가 어떤 예외를 발생시킬 지 알아야한다. 따라서 Checked Exception 을 사용해서 발생 가능한 예외를 명시하도록 강제한다. 반면 Runtime Exception(= Unchecked Exception)은 메소드를 호출하는 쪽에서 대처할 수 있을 것 이라고 예상하기 어렵고, 프로그램 실행 중 매우 빈번하게 일어나기 때문에 명시하도록 강제하면 프로그램의 명확성이 떨어진다고 한다.
결론적으로 메소드를 호출하는 쪽에서 예외를 적절히 처리할 수 있을 것으로 기대될 때 checked exception 으로 만들어야 한다.

예외 처리 방법

  1. 예외가 발생하면 다른 작업 흐름으로 유도하는 예외 복구
  2. 처리하지 않고 호출한 쪽으로 던져버리는 예외처리 회피
  3. 호출한 쪽으로 던질 때 명확한 의미를 전달하기 위해 다른 예외로 전환하여 던지는 예외 전환

try - catch - finally

try {
    ...
} catch (Exception1 e1) {
    ...
} catch (Exception2 e2) {
    ...
} finally{
    ...
}

try 블록 안의 코드를 수행하다가 예외가 발생하면 엮여있는 catch 블락 중 발생한 예외와 일치하는 1개의 catch 블락이 수행된다. 그리고 catch 블락을 나열할 때, 부모 클래스가 자식 클래스보다 먼저 catch에 쓰이게 되면 컴파일 에러가 발생한다. 당연하게도 뒤에서 자식 클래스를 사용한 catch 블락은 진입할 일이 없기 때문이다.
그리고 예외의 발생 여부와 상관 없이 finally 블락의 코드가 수행된다. 이 때, 위에 return문이 있더라도 finally 블락은 반드시 수행된다는 점에 유의해야한다.

int a;
try {
    a = 1 / 0;
}catch(NullPointerException e) {
    System.out.println(e.getClass().getName());
    System.out.println(e.getMessage());
}catch(ArithmeticException e) {
    System.out.println(e.getClass().getName());
    System.out.println(e.getMessage());
}

try문에서 1을 0으로 나누는 예외가 발생하여 catch문을 순서대로 확인한다. NPE는 해당 예외가 아니므로 건너뛰고, 그 다음 ArithmeticException 블락이 수행되어 예외 메세지를 출력한다. getMessage() 메소드의 경우에는 위의 예외 계층 구조에서 Throwable 클래스에 있는 메소드로, 발생한 예외클래스의 인스턴스에 저장된 메시지를 가져올 수 있다.

Multicatch block
JDK 1.7부터 여러 catch 블록을 합쳐서 사용할 수 있다. 이 때, catch문의 예외 클래스들이 부모-자식 관계를 가지면 안 된다. 예외 클래스가 부모-자식 관계를 가지는 것은 결국 코드가 중복되는 것이나 마찬가지기 때문에 컴파일 에러가 발생한다.

int a;
try {
    a = 1 / 0;
}catch(NullPointerException e | ArithmeticException e) {
    System.out.println(e.getClass().getName());
    System.out.println(e.getMessage());
}

예외 객체인 e는 어떤 예외 클래스에 속한 것인지 정확히 알 수 없기 때문에, 두 예외 클래스의 공통 조상 클래스에 속하게 된다.

throws와 throw

public class FoolException extends RuntimeException {
}

public void sayNick(String nick) {
    if("바보".equals(nick)) {
        throw new FoolException();
    }
    System.out.println("당신의 별명은 "+nick+" 입니다.");
}

public static void main(String[] args) {
    sayNick("바보");
    sayNick("바다의 보배");
}

위의 예제에서 throw new FoolException()을 이용해서 강제로 예외를 발생시켰다.

public class FoolException extends Exception {
}

public void sayNick(String nick) {
    try{
        if("바보".equals(nick)) {
            throw new FoolException();
        }
        System.out.println("당신의 별명은 "+nick+" 입니다.");
    }catch(FoolException e) {
        System.err.println("FoolException이 발생했습니다.");
    }
}

여기서 만약 FoolException이 Exception을 상속하게 된다면, Exception은 Checked Exception이므로 반드시 예외처리를 해주어야한다. 따라서 sayNick메소드 내에 try-catch문을 넣어서 컴파일을 가능하도록 했다. 하지만 이렇게하면 sayNick메소드에서 예외를 던지고 그에 대한 처리도 하는 형태가 되는데, 이때 throws를 쓸 수 있다. 즉, throws는 해당 메서드에서 예외를 처리하지 않고, 사용하는 쪽이 예외를 처리하도록 책임을 전가하는 역할을 한다.

public class FoolException extends Exception {
}

public void sayNick(String nick) throws FoolException {
    if("바보".equals(nick)) {
        throw new FoolException();
    }
    System.out.println("당신의 별명은 "+nick+" 입니다.");
}

public static void main(String[] args) {
    try {
        sayNick("바보");
        sayNick("바다의 보배");
    }catch(FoolException e) {
        System.err.println("FoolException이 발생했습니다.");
    }
}

예외처리의 방법을 달리하는 과정에서 결국 코드 실행 방식도 변화하게 되었다. ‘예외처리를 어디서하느냐’에 따라서 sayNick(“바다의 보배”)라는 코드가 실행 될지 말지가 달라진다.

참고문헌

https://itmining.tistory.com/9
https://wisdom-and-record.tistory.com/46
https://wikidocs.net/229

Leave a comment