본문 바로가기
Book/Java의 정석

[Chapter 8] 예외처리(exception handling)

by slchoi 2022. 4. 1.
728x90
SMALL

'자바의 정석 3rd Edition'을 공부한 후 정리한 내용입니다.

1.1 프로그램 오류


  • 프로그램 에러(오류): 프로그램이 실행 중 어떤 원인에 의해서 오작동하거나 비정상적으로 종료되는 경우, 이 결과를 초래하는 원인

컴파일 에러 컴파일 시에 발생하는 에러
런타임 에러 실행 시에 발생하는 에러
논리적 에러 실행은 되지만, 의도와 다르게 동장하는 것

에러(error) 프로그램 코드에 의해서 수습될 수 없는 심각한 오류

  • ex) 메모리 부족, 스택오버플로우

예외(exception) 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

  • 에러가 발생하면, 프로그램의 비정상적인 종료를 막을 길이 없지만, 예외는 발생하더라도 프로그래머가 이에 대한 적절한 코드를 미리 작성해 놓음으로써 프로그램의 비정상적인 종료를 막을 수 있음

1.2 예외 클래스의 계층구조


  • 자바에서는 실행 시 발생할 수 있는 오류(exception, error)를 클래스로 정의

  • 모든 예외의 최고 조상은 Exception 클래스

    사진 출처: https://velog.io/@orpsh1941/JAVA%EC%9D%98-%EC%A0%95%EC%84%9D%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC

  • 예외 클래스는 두 급룹으로 나눠짐

    • Exception 클래스와 자손들
      • 주로 외부의 영향으로 발생. 프로그램의 사용자들의 동작에 의해서 발생
      • ex) 존재하지 않는 파일의 이름을 입력, 실수로 클래스의 이름을 잘못 적음, 입력한 데이터 형식이 잘못된 경우
    • RuntimeException 클래스와 자손들
      • 주로 프로그래머의 실수에 의해 발생될 수 있는 에러들로 자바의 프로그래밍 요소들과 관계가 깊음
      • ex) 배열의 범위를 벗어남, 값이 null인 참조변수의 멤버를 호출, 클래스간의 형변환을 잘못함, 정수를 0을 나누려는 경우

1.3 예외처리하기 - try-catch문


  • 예외처리(exception handling)

    • 정의: 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것
    • 목적: 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행 상태를 유지할 수 있도록 하는 것
  • 발생한 예외를 처리하지 못하면 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외는 JVM의 '예외 처리기'가 받아서 예외의 원인을 화면에 출력

  • 예외 처리를 위해서는 try-catch문을 사용

    try {
      // 예외가 발생할 가능성이 있는 문장을 넣음
    } catch (Exception1 e1) {
      // Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 적음
    } catch (Exception2 e2) {
      // Exception2이 발생했을 경우, 이를 처리하기 위한 문장을 적음
    } catch (Exception3 e3) {
      // Exception3이 발생했을 경우, 이를 처리하기 위한 문장을 적음
    }
  • 하나의 try 블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch 블럭을 받아올 수 있음

    • 발생한 예외의 종류와 일치하는 단 한 개의 catch 블럭만 수행됨
    • 발생한 예외의 종류와 일치하는 catch 블럭이 없으면 예외는 처리되지 않음

1.4 try-catch문에서의 흐름


try 블럭 내에서 예외가 발생한 경우

  1. 발생한 예외와 일치하는 catch 블럭이 있는지 확인
  2. 일치하는 catch 블럭을 찾게 되면, catch 블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져나가 그 다음 문장을 계속해서 수행. 만일 일치하는 catch 블럭을 찾지 못하면, 예외는 처리되지 못함
    • try 블럭에서 예외가 발생하면, 예외가 발생한 위치 이후에 있는 try 블럭의 문장들은 수행되지 않음

try 블럭 내에서 예외가 발생하지 않는 경우

  1. catch 블럭을 거치지 않고 전체 try-catch문을 빠져나가 수행을 계속

1.5 예외의 발생과 catch블럭


  • catch 블럭은 괄호()와 블럭{} 두 부분으로 나눠져 있는데, ()내에는 처리하고자 하는 예외와 같은 타입의 참조변수 하나를 선언
    • 예외가 발생한 문장이 try 블럭에 포함되어 있다면, 이 예외를 처리할 수 있는 catch 블럭이 있는지 찾게됨
  • 첫 번째 catch 블럭부터 차례로 내려가면서 catch 블럭의 괄호 ()내에 선언된 참조변수의 종류와 생성된 예외 클래스의 인스턴스에 instanceof 연산자를 이용해서 검사하게 되는데, 검사결과가 true인 catch 블럭을 만날 때까지 검사는 계속됨
    • 검사 결과가 true인 catch 블럭을 찾게 되면 블럭에 있는 문장들을 모두 수행한 후에 try-catch 문을 빠져나가고 예외는 처리되지만, 검사결과가 true인 catch 블럭이 하나도 없으면 예외는 처리되지 않음
  • 모든 예외 클래스는 Exception 클래스의 자손이므로, catch 블럭의 괄호()에 Exception 클래스 타입의 참조변수를 선언해 놓으면 어떤 종류의 예외가 발생하더라도 이 catch 블럭에 의해 처리

printStackTrace()와 getMessage()

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

  • catch 블럭의 ()에 선언된 참조변수를 통해 이 인스턴스에 접근 가능
  • 이 참조변수로 선언된 catch 블럭 내에서만 사용 가능

멀티 catch블럭

  • JDK 1.7부터 여러 catch 블럭을 '|'기호를 이용해, 하나의 catch 블럭으로 합칠 수 있게 됨
  • 멀티 catch 블럭을 사용하면 중복된 코드를 줄일 수 있음
  • '|' 기호로 연결할 수 있는 예외 클래스의 개수에는 제한이 없음
  • 멀티 catch는 하나의 catch 블럭으로 여러 예외를 처리하는 것이기 때문에, 발생한 예외를 멀티 catch 블럭으로 처리하게 될 경우 실제 어떤 예외가 발생한 것인지 알 수 없음
    • 참조변수 e로 멀티 catch 블럭에 '|' 기호로 연결된 예외 클래스의 공통 분모인 조상 예외 클래스에 선언된 멤버만 사용할 수 있음
  • 멀티 catch 블럭에 선언된 참조변수 e는 상수이므로 값을 변경할 수 없음. 여러 catch 블럭이 하나의 참조변수를 공유하기 때문

1.6 예외 발생시키기


  1. 연산자 new를 이용해서 발생시키려는 예외의 클래스 객체를 만듦
    Exception e = new Exception("고의로 발생시켰음");
  1. 키워드 throw를 이요해서 예외를 발생시킴
    throw e;
  • Exception 인스턴스를 생성할 때, 생성자로 String을 넣어주면, 이 String이 Exception 인스턴스에 메시지로 저장됨

    • 이 메시지는 getMessage()를 이용해서 얻을 수 있음
  • Exception 클래스들이 발생할 가능성이 있는 문장들에 대해 예외처리를 해주지 않으면 컴파일조차 되지 않음

  • RuntimeException 클래스들에 해당하는 예외는 프로그래메에 의해 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않음

    • RuntimeException 클래스들에 속하는 예외가 발생할 가능성이 있는 코드에도 예외처리를 필수로 해야 한다면, 참조 변수와 배열이 사용되는 모든 곳에 예외처리를 해주어야 함
  • 컴파일러가 예외처리를 확인하지 않는 RuntimeException 클래스들은 'unchecked 예외'라고 부르고, 예외처리를 확인하는 Exception 클래스들은 'checked 예외'라고 부름

1.7 메서드에 예외 선언하기


  • 예외를 처리하는 방법은 try-catch문을 사용하는 것 외에, 예외를 메서드에 선언하는 방버비 있음
    void method() throws Exception1, Exception2, ..., ExceptionH {
      // 메서드의 내용
    }
  • 메서드에 예외를 선언하려면, 메서드의 선언부에 키워드 throws를 사용해 메서드 내에서 발생할 수 있는 예외를 적어줌
    • 예외가 여러 개일 경우 쉼표로 구분
    • throw(예외를 발새시키는 키워드)와 구분
  • 메서드의 선언부에 예외를 선언함으로써 메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어져야 하는지 쉽게 알 수 있음
    • 메서드에 예외를 선언할 때 RuntimeException클래스들은 적지 않음
    • throws에는 반드시 처리해주어야 하는 예외들만 선언
  • 예외가 발생한 메서드에서 예외처리를 하지 않고 자신을 호출한 메서드에게 예외를 넘겨줄 수 있지만, 이것은 예외가 처리된 것이 아니라 단순히 전달만 하는 것
    • 어느 한 곳에서는 반드시 try-catch문으로 예외를 처리해줘야 함
    • 예외가 발생한 메서드 내에서 자체적으로 처리해도 되는것은 메서드 내에서 try-catch문을 사용. 메서드에 호출 시 넘겨받아야 할 겂을 다시 받아야 하는 경우(메서드 내에서 자체적으로 해결이 안 되는 경우)에는 예외를 메서드에 선언해서, 호출한 메서드에서 처리해야 함

1.8 finally 블럭


try {
    // 예외가 발생할 가능성이 있는 문장
} catch (Exception1 e1) {
    // 예외처리를 위한 문장
} finally {
    // 예외의 발생여부에 관계없이 항상 수행되어야 할 문장
    // finally 블럭은 try-catch문의 맨 마지막에 위치해야함
}
  • 예외가 발생한 경우: try -> catch -> finally
  • 예외가 발생하지 않은 경우: try -> finally
  • try, catch 블럭이 실행되는 도중 return문이 실행되더라도 finally 블럭의 문장들이 먼저 실행된 후에 현재 실행 중인 메서드를 종료

1.9 자동 자원 반환 - try-with-resources문


  • JDK1.7부터 추가됨
  • 입출력과 관련되 클래스를 사용할 때 유용
  • try-with-resource문의 괄호()안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로 close()를 호출하지 않아도 try 블럭을 벗어나는 순간 자동적으로 close()가 호출됨. 그 다음 catch 블럭 또는 finally 블럭이 실행됨
    • try-with-resource문에 의해 자동으로 객체의 close()가 호출될 수 있으려면, 클래스가 AutoCloseable이라는 인터페이스를 구현한 것이어야 함

예시

class TryWithResourceEx {
    public static void main(String args[]) {
        try (CloseableResource cr = new CloseableResource()) {
            cr.exceptionWork(false); // 예외가 발생하지 않는다.
         } catch(WorkException e) {
            e.printStackTrace();
        } catch(CloseException e) {
            e.printStackTrace();
        }
        System.out.println();
        try (CloseableResource cr = new CloseableResource()) {
            cr.exceptionWork(true); // 예외가 발생한다.
         } catch(WorkException e) {
            e.printStackTrace();
        } catch(CloseException e) {
            e.printStackTrace();
        }    
    } // main의 끝
}
class CloseableResource implements AutoCloseable {
    public void exceptionWork(boolean exception) throws WorkException {
        System.out.println("exceptionWork("+exception+")가 호출됨");
        if(exception)
            throw new WorkException("WorkException발생!!!");
    }
    public void close() throws CloseException {
        System.out.println("close()가 호출됨");
        throw new CloseException("CloseException발생!!!");
    }
}
class WorkException extends Exception {
    WorkException(String msg) { super(msg); }
}
class CloseException extends Exception {
    CloseException(String msg) { super(msg); }
}
/*결과*/
exceptionWork(false)가 호출됨
close()가 호출됨
CloseException: CloseException발생!!!
    at CloseableResource.close(TryWithResourceEx.java:33)
    at TryWithResourceEx.main(TryWithResourceEx.java:6)
exceptionWork(true)가 호출됨
close()가 호출됨
WorkException: WorkException발생!!!
    at CloseableResource.exceptionWork(TryWithResourceEx.java:28)
    at TryWithResourceEx.main(TryWithResourceEx.java:14)
    Suppressed: CloseException: CloseException발생!!!
        at CloseableResource.close(TryWithResourceEx.java:33)
        at TryWithResourceEx.main(TryWithResourceEx.java:15)
  • main 메서드
    • 두 개의 try-catch문 중 첫 번째는 close()에서만 예외를 발생시키고, 두 번째는 exceptionWork()와 close()에서 모두 예외를 발생시킴
  • 첫 번째는 일반적인 예외가 발생했을 때와 같은 형태로 출력되지만, 두 번째는 출력 형태가 다름
    • 두 예외가 동시에 발생할 수 없기 때문에, 실제 발생한 예외를 WorkException으로 하고, CloseException은 억제된(Suppressed) 예외로 다룸.
    • 억제된 예외에 대한 정보는 실제로 발생한 예외인 WorkException에 저장됨
  • 기존의 try-catch문에서는 finally 블럭에 try-catch문을 추가해 예외를 처리해줄 수 있지만, 만약 try 블럭과 finally블럭에서 모두 예외가 발생하면 try블럭의 예외는 무시됨

1.10 사용자정의 예외 만들기


  • Exception 클래스 또는 RuntimeException 클래스로부터 상속받아 클래스를 만들지만, 필요에 따라 알맞은 예외 클래스 선택이 가능

1.11 예외 되던지기(exception re-throwing)


  • 한 메서드에서 발생할 수 있는 예ㅚ가 여럿인 경우, 몇 개는 try-catch문을 통해 메서드 내에서 자체적으로 처리하고, 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 함으로써, 양쪽에 나눠서 처리되도록할 수 있음

  • 예외 되던지기: 예외를 처리한 후에 인위적으로 다시 발생시키는 방법

    • 예외가 발생할 가능성이 있는 메서드에서 try-catch문을 사용해서 예외를 처리해줌
    • catch문에서 필요한 작업을 행한 후 throw문을 사용해서 예외를 다시 발생시킴
    • 다시 발생한 예외는 메서드를 호출한 메서드에게 전달되고 호출한 메서드의 try-catch문에서 예외를 또 다시 처리
  • 하나의 예외에 대해 예외가 발생한 메서드와 이를 호출한 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을 때 사용

    • 예외가 발생할 메서드에서는 try-catch문을 사용해서 예외처리를 해줌과 동시에 메서드의 선언부에 발생할 예외를 throws에 지정해줘야 함
  • 반환값이 있는 return문의 경우 catch블럭에도 return문이 있어야 함. 예외가 발생했을 경우에도 값을 반환해야하기 때문

    • catch 블럭에서 예외 되던지기를 해서 호출한 메서드로 예외를 전달하면, return문은 없어도 됨

1.12 연결된 예외(chained exception)


  • 한 예외가 다른 예외를 발생시킬 수 있음
    • 예외 A가 예외 B를 발생시켰다면, A를 B의 '원인 예외(cause exception)'이라고 함

Throwable initCause(Throwable cause) 지정한 예외를 원인 예외로 등록
Throwable getCause() 원인 예외를 반환

발생한 예외를 원인 예외로 등록한 후 다시 예외를 발생시키는 이유

  1. 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위함
  2. checked 예외를 unchecked 예외로 바꿀 수 있게 하기 위함
    • checked 예외로 예외처리를 강제한 이유는 프로그래밍 경험이 적은 사람도 견고한 프로그램을 작성할 수 있도록 유도하기 위한 것
    • 현재는 컴퓨터 환경이 많이 달라져, checked 예외가 발생해도 예외를 처리할 수 없는 상황이 발생하기 시작
    • 이럴 때 의미없는 try-catch문을 추가할 필요없이 checked 예외를 unchecked 예외로 바꾸면 예외처리가 선택적이 되므로 억지로 예외 처리를 하지 않아도 됨

Chapter 8 끝!!!

728x90
LIST

댓글