본문 바로가기

자바

예외처리

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

try/catch

exception이 발생했을 때, 따로 처리하고 싶을 때 사용한다. 아래와 같이 사용할 수 있다.

try {
    System.out.println("here is try block");
    // 예외가 발생할 만한 블락
    // 예외를 잡아서 처리하고 싶은 부분
} catch (RuntimeException e) { // 해당하는 예외가 발생했을 경우 
    // RuntimeException 에 대한 예외 처리 
} catch (Exception e) {
    // Exception 에 대한 예외 처리
} 
// multi catch 문은 위에서부터 작은 exception 을 선언해야 한다.
// 아래로 갈수록 범위가 큰 exception 을 선언해야 한다.
// try 문에서 catch 문에 해당하지 않는 exception 이 발생 시 try/catch 문은 동작하지 않는다.

여러 exception 을 catch 를 했더니 해야 하는 일이 비슷하다면 java 1.7 부터는 multi catch 를 사용할 수 있다.

try {
	throw new NullPointerException();
} catch (NullPointerException | IllegalArgumentException e) { // multi catch
  // multi catch 에 있는 예외들이 상속 관계라면 컴파일 에러가 발생한다.
}

throw

명시적으로 특정 exception을 발생시키는 키워드이다.

throws

메소드 선언부에 사용한다. 이 메소드가 특정 exception 을 던진다는 것을 명시적으로 적어주며, exception handling 을 caller 에게 위임한다.

그래서 throws 키워드가 붙은 함수를 호출하는 경우에 처리해줘야 하는 두 가지가 있다.

  1. try/catch 문을 사용하여 exception을 핸들링한다.
  2. 호출하는 메소드에도 throws 키워드를 사용하여 또 다시 caller 에게 위임한다.
// ExceptionHandler.java
public class ExceptionHandler {
    public ExceptionHandler() {}

    public void throwException() throws IOException {
        throw new IOException();
    }
}
// 1번 방법
public static void main(String[] args) {

    ExceptionHandler exceptionHandler = new ExceptionHandler();
    try {
        exceptionHandler.throwException();
        // 예외가 발생할 만한 블락
        // 예외를 잡아서 처리하고 싶은 부분
    } catch (IOException e) {
        // IOException 에 대한 예외 처리
    }
}
// 2번 방법
public static void main(String[] args) throws IOException { // throws 키워드를 사용한다.

    ExceptionHandler exceptionHandler = new ExceptionHandler();
    exceptionHandler.throwException();
}

finally

optional 하게 사용할 수 있다. exception 이 발생하든, 안 하든 상관없이 호출되어야 하는 로직이 있을 때, 사용한다.

catch 에서 return 을 한다면 finally 는 어떻게 될까?

finally 에서 return 은 안티 패턴이다.

finally 에서는 흐름을 바꾸는 일, 예외를 던지거나 리턴을 하거나 하는 일은 자제? 그래야 try/catch 가 의미가 있어지니까?

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

자바는 위와 같이 예외 계층 구조를 가진다.

그림을 보면서, 중요하게 여겨볼 부분은 다음과 같다.

Exception 과 Error 클래스들이 Throwable 클래스를 상속받고 있는 데, 이 Throwable 클래스는 어떤건가?

Exception 과 Error 는 각각 무엇이고, 어떤 차이점이 있을까?

노란색인 Exception 과 빨간색 Exception 들은 무엇을 의미하고, 어떤 차이점이 있을까? → checkedException 과 uncheckedException 은 어떤 차이가 있을까?

Throwable

Exception, Error 클래스가 상속받는 최상위 클래스이다. 다음의 메소드들을 가지고 있다.

public String getMessage() // 발생한 throwable 객체의 detailMessage 정보를 가져온다. 세팅되어있지 않다면 null 을 반환한다.
public synchronized Throwable getCause() // 발생한 throwable 객체의 원인이 되는 Throwable 객체를 반환한다. 세팅되어있지 않다면 null 을 반환한다.

이 메소드들 이외에도 메소드들은 있지만, 개인적으로 많이 쓰이는 메소드라고 생각한다.

throwable.printStackTrace() 메소드도 있지만, 이 메소드는 사용하지 않는 것을 권장하고 있다. 이유는 따로 정리해보겠다.

Exception v.s. Error

Error

프로그램이 실행 도중 비정상적인 상태를 말한다. 정상적인 프로그램이라면 핸들링해서는 안되는 클래스이다.

Exception

프로그램이 실행 도중 비정상적인 상태를 말한다. 프로그램에서의 핸들링 대상이 된다.

둘 다 프로그램이 실행되는 도중 일련의 비정상적인 상태를 말한다. 핸들링의 대상이 되느냐, 되어서는 안되냐의 차이가 있다.

Checked Exception v.s. Unchecked Exception

Checked Exception 이란?

RuntimeException 을 상속받지 않는 예외들을 말한다. 위 그림에서 노란색 예외들이라고 할 수 있겠다. 이러한 예외들은 반드시 try/catch 나 throws 키워드를 통해 에러 처리를 해줘야 한다. 그렇지 않으면 컴파일 에러가 발생한다.

자바가 checked Exception 핸들링을 강제한 이유 중에 하나는 복구를 위해서이다.

  • IOException, ClassNotFoundException 등이 있다.

Unchecked Exception:

RuntimeException 을 상속받는 예외들을 말한다. 위 그림에서 빨간색 예외들이라고 할 수 있겠다. NullPointerException 이나 ArrayIndexOutOfBoundsException 같은 에러들이 Runtime Exception 을 상속받는다. 이러한 예외들을 만약 예외 처리를 강제한다면, 객체나 배열에 접근할 때마다 예외 처리를 해줘야 하기 때문에 강제하지 않는다.

Suppressed Exception

무시되는 예외를 뜻한다. 보통 try/catch 문에서 finally 블록에서 발생할 수 있다. 다음의 예제를 보자.

public static void main(String[] args) {

        ExceptionHandler exceptionHandler = new ExceptionHandler();
        String nullString = null;
        Exception occuredException = null;
        try {
					throw new IOException("io exception"); // IOException 발생
        } catch (RuntimeException | IOException e) {
            occuredException = e;
        } finally {
            try {
                nullString.equals("test"); // NullPointerException 발생
            } catch (Exception e) {
                if (occuredException != null) {
                    e.addSuppressed(occuredException); // Suppressed Exception 추가
                }
                throw e;
            }
        }

    }

이 코드를 실행하게 되면

Exception in thread "main" java.lang.NullPointerException
	at me.screw.javademostudy.exceptionhandling.ExceptionDemoApplication.main(ExceptionDemoApplication.java:16)
	Suppressed: java.io.IOException: io exception
		at me.screw.javademostudy.exceptionhandling.ExceptionDemoApplication.main(ExceptionDemoApplication.java:11)

이렇게 나오게 된다. 이 에러로그를 해석하게 되면 아래와 같다.

NullPointerException 이 발생되었고, 그 전에 IOException 이 발생했지만 무시되었다.

 

개인적인 생각으로는 개발자가 Suppressed Exception 을 핸들링 하는 경우는 많이 없을 것 같다는 생각이 든다. 하지만, 이런 게 있다는 것을 알고 있으면 문제가 발생했을 때, 에러로그를 보며 문제 원인을 파악할 때에 도움이 될 수 있을 것 같다.

 

 

ref

'자바' 카테고리의 다른 글

Enum  (2) 2024.03.15
멀티쓰레드 프로그래밍  (0) 2024.02.24
자바 패키지  (0) 2023.12.16
자바 인터페이스  (1) 2023.11.13
자바 상속  (1) 2023.10.23