정의

프로그램 실행 중에 발생하는 이벤트이면서 프로그램 로직의 정상적인 흐름을 방해하는 오류를 말한다.

우리는 그냥 '예외(Exception)' 라고만 부르는데, 사실 '예외적 이벤트(Exceptional Event)'의 줄임말이다.

목적

  • 예외를 복구하여 프로그램을 진행
  • 예외가 발생한 프로그램을 중단하고 운영자나 개발자에 통보
  • 예외가 발생하기 전에 개발자가 코드로 미리 예외에 대한 로직을 추가해서 대응 

 

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

https://www3.ntu.edu.sg/home/ehchua/programming/java/J5a_ExceptionAssert.html

모든 예외는 java.lang.Throwable 클래스를 상속한다.

그래서 Error나 Exception을 처리할 때 Throwable로 처리해도 무관하다.

 

예외 종류

컴파일러의 예외 탐지(check) 여부를 기준으로 아래처럼 구분한다.

  • Checked Exception (Compiletime Exception, 컴파일 예외)
    예) SQLException, FileNotFoundException
  • Unchecked Exception (Runtime Exception, 런타임 예외)
    예) NullPointerException, ArrayIndexOutOfBoundException

 

에러(Error)와의 비교

에러발생 시 복구할 수 없는 심각한 오류를 의미한다.

발생 원인은 자바 프로그램일지라도 그 원인을 프로그램에서 바로 해결하거나 에러를 복구하는 것은 불가능하다.

예) StackOverflowError, OutOfMemoryError

 

💡 보통의 개발자들은 실무에서 'Checked Exception', '컴파일 예외'라는 말보단 '컴파일 에러(?)' 라고 말해서
그동안 에러와 예외의 경계를 애매하게 알고있었는데 이 설명으로 확실히 구분해두려 한다.

 

예외를 두 타입으로 나눠둔 이유 - 호출하는 쪽에서 예외를 대비할 수 있게

예외는 메서드의 파라미터나 반환 값만큼이나 중요한 공용 인터페이스 중 하나이다.

메서드를 호출하는 쪽은 그 메서드가 어떤 예외를 발생시킬 수 있는가에 대해 반드시 알아야 한다.

따라서 Java는 Checked Exception을 통해 해당 메서드가 발생시킬 수 있는 예외를 명세하여 대비하도록 강제하고 있다.

 

💡 오라클의 예외 관련 여러 공식문서에서는 예외를 Specify(명세) 한다고 표현하는데,
더 적절한 번역 단어가 떠오르지 않았고 찾지 못했다. 어감이 어색하다면 양해 바란다.

 

💡 그럼 Runtime Exception은 왜 예외를 명세하지 않아도 되도록 했을까?

Runtime Exception은 프로그램 코드의 문제로 발생하는 예외이다.

 

따라서 클라이언트 쪽(메서드를 호출하는 쪽)에서 이를 복구(or 회복)하거나 대처할 수 있을 거라고 예상하긴 어렵다.

또 Runtime Exception은 프로그램 어디서나 매우 빈번하게 발생할 수 있기 때문에

모든 Runtime Exception을 메서드에 명시하도록 강제하는 것은 프로그램의 명확성을 떨어뜨릴 수 있다.

 

따라서 클라이언트가 Exception을 적절히 회복할 수 있을 것이라고 예상되는 경우 Checked Exception으로 만들고,

그렇지 않은 경우 Unchecked Exception으로 만드는 것이 좋다.

 

위 내용에 대한 오라클 공식 문서 원문

 

Unchecked Exceptions — The Controversy (The Java™ Tutorials > Essential Classes > Exceptions)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

예외 처리 방법

try{} - catch(){}

try {
    System.out.println(1 / 0); // 0으로 나누면 ArithmeticException 발생
}
catch (IllegalArgumentException e) { // 예외가 catch되지 않았으므로 다음 구문으로
    System.out.println(e.getClass().getName());
    System.out.println(e.getMessage()); }
catch (ArithmeticException e) { // 여기서 ArithmeticException catch됨
    System.out.println(e.getClass().getName());
    System.out.println(e.getMessage()); }
catch (NullPointerException e) { // 위에서 예외가 catch됐으므로 실행되지 않음
    System.out.println(e.getClass().getName());
    System.out.println(e.getMessage());
}

작성법은 위와 같으며, 이 중 발생한 예외 종류와 일치하는 단 한 개의 catch 블록만 수행된다.

catch 블록의 소괄호 안에는 예외 클래스와 해당 클래스의 인스턴스를 가리키는 참조 변수가 있다.

 

try 블록 내에서 예외를 발생시킬 경우에는 throw를 적고 예외 객체를 생성하거나 생성된 예외 객체를 명시한다.

이때 catch 블록에 선언되지 않았거나 throws 선언에 포함되지 않았다면 컴파일 에러가 발생한다.

catch 블록에서 예외를 throw할 경우에도 메서드 선언의 throws 구문에 해당 예외가 정의돼있어야 한다.

 

finally

예외가 발생한 경우에는 try -> catch -> finally 순으로 실행되고,

예외가 발생하지 않은 경우에는 try - finally 순으로 실행된다.

심지어 try문에 return 문이 있더라도 마찬가지이다.

try {
// 예외가 발생할 가능성이 있는 문장을 넣는다.
}
catch {
// 예외 처리를 위한 문장을 넣는다.
}
finally {
// 예외 발생 여부와 상관없이 항상 실행되어야 할 문장을 넣는다.
}

 

try - with - resource

Java 7 부터 추가된 것이며 Exception 발생시 resources를 자동으로 close() 해준다.

이게 가능한 이유는 try(코드)를 인식해서 close() 필요한 코드가 있으면,

바이트코드를 조작해서 close()를 생성하는 것이다.

단, 로직 작성 시 객체는 AutoCloseable 인터페이스를 구현한 객체여야한다.

try(FileInputStream fis = new FileInputStream("절대_없는_파일.txt")){
    // try() Exception 발생시 바로 fis.close() 메소드 실행하고 catch 로
    // 명시적으로 fis.close() 를 적어주지 않는다.
    System.out.println(fis.read());
} catch(IOException e) {
    e.printStackTrace();
}

// 디컴파일 클래스
try {
    FileInputStream fis = new FileInputStream("절대_없는_파일.txt");

    try {
        System.out.println(fis.read());
    } catch (Throwable var5) { // -> catch 블록 생성
        try {
            fis.close(); // -> close() 생성
        } catch (Throwable var4) {
            var5.addSuppressed(var4);
        }

        throw var5;
    }
    
    fis.close(); // -> catch 블록 안들어와도 close() 되도록 생성
}

 

Multicatch block

Java 7부터 여러 catch 블록을 하나로 합칠 수 있게 됐다.

단, 나열된 예외 클래스들이 부모-자식 관계에 있다면 오류가 발생한다.

try {
    System.out.println(1 / 0);
}
catch (RuntimeException | ArithmeticException e)
{
    // ArithmeticException에 아래와 같은 오류 발생
    // Types in multi-catch must be disjoint: 'java.lang.ArithmeticException'
    // is a subclass of 'java.lang.RuntimeException'
    System.out.println(e.getMessage());
}

IntelliJ에서 이를 해결하려고 Quick fix 기능을 활용해봤다.

그 결과 아래처럼 예외 종류별로 catch 블록을 나누더라도

하위 클래스가 잡히지 않는 상황이 발생하므로 역시 오류가 발생한다.

catch 구문의 순서도 신경 써야 한다.

 

throws

메서드 선언 시 변수 소괄호 뒤에 throws를 적고 예외를 선언하면,

해당 메서드에서 선언한 예외가 발생했을 때 호출한 메서드로 예외가 전달된다.

두 가지 이상의 예외를 던질 수 있다면 콤마(,)로 구분한다.

try
{
    if (nickname.containsBannedWords())
    {
        throw new IllegalArgumentException("부적절한 단어입니다.");
    }
}
catch (IllegalArgumentException e)
{
    System.out.println(e.getMessage());
}

 

throw

void method() throws Exception1, Exception2, ... ExceptionN { // 메서드 내용 }

thorws는 메서드 선언부에 예외를 선언해둠으로써

해당 메서드를 사용하는 사람들이 어떤 예외를 처리해야 하는 지를 알려주는 역할을 한다.

 

throws 자체는 예외의 처리와는 관계가 없다.

throws로 예외가 선언된 메서드를 사용할 때, 사용자가 각자 알아서 예외를 처리해줘야 한다.

즉 throws는 해당 메서드에서 예외를 처리하지 않고,

해당 메서드를 사용하는 쪽이 예외를 처리하도록 책임을 전가하는 역할을 한다.

 

 

예외 처리 시 주의사항

💡 catch 블록에서 예외를 잡았지만 아무런 처리를 하지 않는 것은 반드시 피해야 한다.

잡은 예외가 어디서 왜 발생했는지 찾을 수 있는 정보가 나타나지 않기 때문이다.

 

💡 운영서버로 코드를 배포할때는 시스템 출력 메서드를 사용하지 않고 별도의 log에 남겨야 한다.

아래와 같은 예외 관련 시스템 출력 메서드가 있는데, 주로 개발서버에서 예외 메시지 확인 용도로만 사용한다.

  • printStackTrace()
    예외 발생 당시의 호출 스택에 있었던 메서드 정보와 예외 메시지를 화면에 출력한다.
  • getMessage()
    발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.

그런데, 이런 예외를 시스템에 출력해버리면 쉽게 읽을 수 있고 외부에서 이를 악용하는 사람은 예외를 모아서 프로그램의 취약점을 파고들 수 있기 때문에 조심해야 한다.


출처

예외 정의

docs.oracle.com/javase/tutorial/essential/exceptions/definition.html

 

예외와 에러의 차이

Multicatch block

예외를 두 개의 타입으로 나눈 이유

https://wisdom-and-record.tistory.com/46

 

Runtime Exception의 예외를 명세하지 않아도 되도록 한 이유

docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html

 

커스텀 Exception

https://dzone.com/articles/implementing-custom-exceptions-in-java?fromrel=true

 

'자바의 신' 책 예외 부분

www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788997924325