8. 예외처리

KOO HEESEUNG·2021년 9월 30일
0

Java의 정석

목록 보기
3/8
post-thumbnail

8. 예외처리

1. 프로그램 오류

종류의미
컴파일 에러컴파일할 때 발생하는 에러
런타임 에러프로그램 실행 중에 발생하는 에러
논리적 에러의도와 다르게 동작하는 에러

런타임 에러는 다시 에러와 예외로 구분된다.

  • 에러(Error) : 수습할 수 없는 심각한 오류
  • 예외(Exception) : 수습 가능한 미약한 오류

예외 클래스의 계층구조

Throwable은 모든 오류의 최고 조상 클래스이며, Exception은 모든 예외 클래스들의 최고 조상이다.

예외는 크게 Exception 클래스와 그 자손 클래스(①) / RuntimeException 클래스와 그 자손 클래스(②)로 구분된다. ①은 사용자 실수 등의 외적 요인으로 발생하는 예외이며, ②는 프로그래머의 실수로 발생하는 예외이다.

①의 경우, checked 예외이기 때문에 반드시 예외 처리를 해주어야 하는 반면, ②는 unchecked 예외이기 때문에 예외 처리가 선택적이다.

왜 checked 예외와 unchecked 예외로 구분할까?

모든 참조변수를 사용할 때, NullPointerException과 같은 예외가 발생할 수 있고, 배열에서도 IndexOutOfBoundsException 등의 예외가 발생할 수 있는데, 이러한 예외를 일일이 예외처리 해주도록 강제하면 너무 번거롭고 복잡하기 때문.


2. 예외 처리

프로그램 실행 시 발생할 수 있는 예외에 대비한 코드를 작성하여 프로그램의 비정상 종료를 방지한다.

예외 처리 방법에는 try-catch문과 메서드에 예외 선언, 예외 되던지기, 그리고 은폐하는 방법 등이 있다.

1. try-catch 문

try {
  // 예외 발생 가능성이 있는 문장 작성.
} catch (Exception1 e1) {
  // Exception1 발생했을 경우, 처리할 문장 작성.
} catch (Exception2 e2) {
	// Exception2 발생했을 경우, 처리할 문장 작성.
} 

try에서 예외가 발생하면 해당 예외를 catch에서 찾아 처리하고, try-catch문을 빠져나간다. 일치하는 catch 블럭을 찾지 못할 경우, 예외가 처리되지 않는다.

try에서 예외가 발생하지 않는다면 try-catch문을 빠져나가 다음 작업을 수행한다.

try {
	...
} catch (ArithmeticException ae) { // (1)
  ...
} catch (Exception e) { // (2)
	...  
}

Exception은 모든 예외의 최고 조상이므로, Exception 클래스 타입의 참조변수를 catch 블럭 괄호에 선언할 경우, 모든 종류의 예외를 해당 catch 블럭이 처리한다.

그러나, 위 코드와 같이 Exception 타입이 선언된 catch 블럭 이전에 다른 예외 타입의 catch 블럭이 존재한다면, 해당 타입의 예외를 제외한 모든 예외를 처리한다.(위 코드에서는 ArithmeticException이 발생할 경우, (1)에서 처리 후, 블럭을 빠져나간다.)

Exception 타입은 가장 마지막 catch 블럭에 선언하는 것이 바람직하다.

멀티 catch블럭

try {
  ...
} catch (Exception1 | Exception2 e) {
  ...
}

Exception1과 Exception2 의 catch블럭 내용이 동일하다면, '|' 기호를 사용하여 하나의 catch블럭으로 묶을 수 있다.

멀티 catch 블럭에서는 묶인 예외들의 공통된 메서드만 사용이 가능하다.

멀티 catch블럭으로 묶은 예외들이 서로 조상-자손 관계라면 컴파일 에러가 발생한다.(조상 클래스만 써주면 됨.)

printStackTrace()와 getMessage()

printStackTrace()와 getMessage()는 예외 클래스 인스턴스에 들어있는 메서드로, 이 메서드를 사용하여 발생한 예외에 대한 정보를 얻을 수 있다.

printStackTrace()예외발생 시 호출스택에 있던 메서드 정보와 예외 메시지 출력
getMessage()발생한 예외클래스 인스턴스에 저장된 메시지 출력

finally 블럭

예외 발생여부와 관계없이 반드시 수행되어야 하는 코드를 넣는다. 반드시 try-catch문 맨 마지막에 위치시킨다.

try {
  ...
} catch (Exception e) {
  ...
} finally {
  ...
}

2. 메서드에 예외 선언(예외 떠넘기기)

void method() throws Exception1, Exception2, ... {}

메서드 선언부에 throws 키워드를 사용하여 해당 메서드에서 발생할 수 있는 예외를 적어준다. 예외가 여러 개일 경우 콤마(,) 로 구분한다.

throws Exception 은 모든 예외가 발생 가능하다는 뜻. 여러 예외를 선언하는 것보다 Exception 하나를 선언하는 것이 예외의 개수가 더 많다.
👉 예외 선언 시, 상속관계도 고려해야 함.(오버라이딩 조건 중 ""예외처리는 조상 클래스의 메서드보다 많이 선언할 수 없다")

예외 발생시키기

키워드 throw를 사용하여 고의로 예외를 발생시킬 수 있다.
new 연산자를 사용하여 발생시킬 예외 클래스 객체를 생성하고, throw를 사용해 예외를 발생시킨다.(객체 생성만으로는 예외 발생 X)


3. 예외 되던지기

예외를 처리한 후 다시 예외를 발생시키는 것. 예외 분담처리.

하나의 예외에 대해 예외가 발생한 메서드와 이를 호출한 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을 때 사용.

class Example {
  public static void main(String[] args) {
    try {
      method1();
    } catch (Exception e) {
      System.out.println("main 메서드에서 예외 처리"); // (3) 다시 예외 처리
    }
  }
  
  static void method1() throws Exception {
    try {
      throw new Exception();
    } catch (Exception e) {
      System.out.println("method1 메서드에서 예외 처리"); // (1) 예외 처리
      throw e; // (2) 다시 예외 발생
    }
  }
}

4. 사용자 정의 예외 만들기

프로그래머가 직접 새로운 예외 클래스를 정의하여 사용 가능하다. 조상은 Exception과 RuntimeException 중 선택한다.

class MyException extends Exception {
  MyException(String msg) { // 예외 메시지를 매개변수로 받는 생성자를 꼭 만들고, 
    super(msg); // 그 안에는 조상 생성자를 호출하도록 작성한다.
  }
}

가능하면 선택처리가 가능한 RuntimeException을 조상으로 하고, 꼭 필요한 경우에만 필수처리 예외(Exception)를 조상으로 하도록 하자.


3. 연결된 예외

예외 안에 또다른 예외를 포함시키는 것.
예외 A가 예외 B를 발생시키면, A는 B의 원인 예외.

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

void install() throws InstallException {
  try {
    startInstall(); // SpaceException 발생시
    copyFiles();
  } catch (SpaceException e) {
    InstallException ie = new InstallException("설치중 예외 발생!"); // 예외생성
    ie.initCause(e); // 생성된 예외를 SpaceException의 원인 예외로 지정
    throw ie; // InstallException 발생시킴
  }
}

사용하는 이유

  1. 여러 예외를 하나로 묶어서 다루기 위해
    • 발생할 수 있는 예외가 많으면 catch 블럭이 너무 많아짐.
    • 여러 예외를 합쳐서 catch블럭을 줄일 수 있음.
  2. checked 예외를 unchecked 예외로 변경하기 위해
    • 필수처리 예외를 선택처리로 변경하려 할 때, 상속한 조상을 변경하면 되지만, 이미 많이 쓰이고 있는 경우 예외의 상속 계층도를 변경시키기가 쉽지 않음.
    • 이 때 연결된 예외를 사용하여 RuntimeException 안에 선택처리로 변경하고 싶은 예외를 집어넣어 RuntimeException이 발생한 것처럼 위장하면 된다.

0개의 댓글