[Java] 11. 예외 처리

JK·2024년 4월 18일
0

[Java]

목록 보기
11/11

예외와 예외 클래스

에러(error)는 컴퓨터 하드웨어의 고장으로 인해 응용 프로그램 실행 오류가 발생하는 것을 말하고, 예외(Exception)는 잘못된 사용 또는 코딩으로 인한 오류를 말한다.

에러나 예외는 모두 공통적으로 발생시 곧바로 프로그램이 종료되지만, 예외는 예외 처리를 통해 계속 실행 상태를 유지할 수 있다.

예외에는 일반 예외(Exception)와 실행 예외(Runtime Exception)가 있다. 일반 예외는 컴파일러가 예외 처리 코드를 검사하는 예외이고, 실행 예외는 컴파일러가 예외 처리 코드 여부를 검사하지 않는 예외이다.

자바는 예외 발생 시 예외 클래스로부터 객체를 생성하여 예외를 처리할 때 사용한다. 자바의 모든 에러와 예외 클래스는 Throwable을 상속받아 만들어지고, 추가적으로 예외 클래스는 java.lang.Exception 클래스를 상속받는다.

실행 예외는 RumtimeException과 그 자식 클래스에 해당하고, 그 밖의 예외는 모두 일반 예외에 해당한다.

자바는 자주 사용되는 예외 클래스를 표준 라이브러리로 제공한다.

예외 처리 코드

예외 처리 코드는 예외가 발생했을 때 프로그램의 갑작스러운 종료를 막고 정상 실행을 유지할 수 있도록 처리하는 코드를 말한다. try-catch-finally 블록으로 구성되고, 생성자 내부와 메소드 내부에서 작성된다.

try 블록에서 작성한 코드가 예외없이 정상 실행되면 catch 블록은 실행되지 않고, finally 블록은 예외 발생 여부와 상관없이 항상 실행된다. 만약 try 블록, catch 블록에서 return 문을 사용하여 메소드를 종류한 경우에도 finally 블록은 실행된다. 이 finally 블록은 사용하지 않을 경우 생략이 가능하다.

try {
  예외가 발생할 수 있는 실행문;
} catch(예외클래스 e) {
  예외처리;

  // 예외 정보를 얻는 방법 세가지
  System.out.println(e.getMessage());
  System.out.println(e.toString());
  e.printStackTrace();
} finally {
  마무리실행문;
}

try문에서 예외가 발생하면 예외 객체가 catch 선언부의 예외 클래스 변수에 대입되고, catch 선언부에서 예외 객체를 받아 예외를 처리한다.

예외 정보를 얻는 방법은 세 가지가 있다. e.getMessage() 메소드는 예외가 발생한 이유만 리턴하고, e.toString() 메소드는 예외의 종류도 함께 리턴한다. e.printStackTrace() 메소드는 예외가 어디서 발생했는지 추적한 내용까지 출력한다.

컴파일러에서 검사하는 일반 예외가 발생할 수 있는 실행문은, 예외 처리 코드를 작성하지 않으면 컴파일되지 않기 때문에 반드시 예외 처리 코드를 작성해야 한다.

예외 종류에 따른 처리

다중 catch를 사용 시 발생하는 예외에 따라 예외 처리 코드를 다르게 작성할 수 있다. catch 블록의 예외 클래스는 try 블록에서 발생된 예외의 종류로, 해당 타입의 예외가 발생하면 catch 블록이 선택되어 실행된다.

catch 블록이 여러 개여도 catch 블록은 단 하나만 실행되는데, 하나의 예외가 발생하면 즉시 실행을 멈추고 해당 catch 블록으로 이동하기 때문이다.

예외 발생 시 catch 블록은 위에서부터 차례대로 검사 대상이 되기 때문에 하위 클래스의 예외를 처리하는 catch 블록을 위에 작성하고, 상위 클래스의 예외를 처리하는 catch 블록을 아래쪽에 작성해야 한다.

리소스 자동 닫기

리소스(resource)는 데이터를 제공하는 객체를 말한다. 리소스는 사용하기 위해 리소스를 열고(open), 사용이 끝난 뒤에 닫는(close) 과정이 필요하다.

리소스를 사용하다가 예외가 발생될 경우, 안전하게 닫지 않으면 리소스가 불안정한 상태로 남을 수 있다.

FileInputStream fis = null;
try {
  fis = new FileInputStream("file.txt");  // 파일 열기
  ...
} catch(IOException e) {
  ...
} finally {
  fis.close();  // 파일 닫기
} 

위와 같이 try-catch-finally 블록을 이용해서 예외가 발생할 경우 리소스를 안전하게 닫도록 직접 코드를 작성할 수 있지만, 아래와 같이 try-with-resources 블록을 사용 시 예외 발생 여부와 상관없이 리소스를 자동으로 닫아준다.

try(FileInputStream fis = new FileInputStream("file.txt")) {
  ...
} catch(IOException e) {
  ...
} 

try-with-resources 블록을 사용하기 위해서 리소스는 java.lang.AutoCloseable 인터페이스를 구현해서 AutoCloseable 인터페이스의 close() 메소드를 재정의해야 한다.

여러 개의 리소스를 try-with-resources 블록을 통해 사용할 경우 try() 괄호 안에 세미콜론(;)으로 구분하여 여러 개의 리소스 여는 코드를 작성한다.

Java 8 이전 버전까지는 try 괄호 안에서 반드시 리소스 변수를 선언해야 했지만, Java 9 버전 이후부터는 외부 리소스 변수를 사용할 수 있게 되었다.

예외 떠넘기기

리턴타입 메소드명(매개변수, ...) throws 예외클래스1, 예외클래스2, ... {
}

메소드 내부에서 예외가 발생할 때 throws 키워드를 사용하여 메소드를 호출한 곳으로 예외를 떠넘길 수 있다. 이 경우 메소드를 호출한 곳에서 예외를 받아 처리한다.

throws는 메소드 선언부 끝에 작성하고 떠넘길 예외 클래스를 쉼표로 구분하여 나열한다. 나열해야 할 예외 클래스가 많은 경우 throws Exception 또는 throws Throwable 만으로 모든 예외를 떠넘길 수 있다.

main() 메소드에서 예외를 떠넘길 경우 JVM에서 최종적으로 예외 처리를 하게 된다. JVM은 예외의 내용을 콘솔에 출력하는 것으로 예외 처리를 한다.

사용자 정의 예외

사용자 정의 예외는 표준 라이브러리에 존재하지 않는 예외를 직접 정의한 것이다. 컴파일러가 체크하는 일반 예외로 선언하거나, 컴파일러가 체크하지 않는 실행 예외로 선언할 수 있다.

통상적으로 사용자 정의 예외도 일반 예외는 Exception의 자식 클래스로, 실행 예외는 RuntimeException의 자식 클래스로 선언한다.

public class XXXException extends [ Exception | RuntimeException ] {
  // 기본 생성자
  public XXXException() {
  }

  // 예외 메시지를 입력받는 생성자
  public XXXException(String message) {
    super(message);
  }
}

사용자 정의 예외 클래스에는 위와 같이 기본 생성자와 예외 메시지를 입력받는 생성자를 선언한다. 예외 메시지는 예외 객체의 공통 메소드인 getMessage() 메소드의 리턴값으로 사용하기 위해 부모 생성자의 매개값으로 넘겨 받는다.

void method() throws Exception {
  ...
  throw new Exception("예외메시지");
  ...
}

직접 코드에서 예외를 발생시키려면 위와 같이 throw 키워드와 함께 예외 객체를 생성자 매개값으로 전달한다. throw된 예외는 try-catch 블록으로 직접 예외 처리를 할 수도 있지만 대부분 메소드를 호출한 곳에서 예외를 처리하도록 throws 키워드로 예외를 떠넘긴다.


"한빛 미디어 출판 도서, 이것이 자바다"를 읽고 학습한 내용을 토대로 작성되었습니다.

0개의 댓글