6. 예외 처리

에러와 예외의 구분

에러(Error)

  • 하드웨어의 고장, 오동작은 에러(Error)라고 한다.

  • 자바 코드와 상관없이 JVM이 실행 불능이 된다.

예외(Exception)

  • 사용자의 잘못된 조작, 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류를 말한다.

에러와 예외의 공통점

  • 처리되지 않은 예외나 에러가 터지면 프로그램이 곧바로 종료된다.

에러와 예외의 차이점

  • 예외는 예외 처리(Exception Handling)를 통해 프로그램이 종료되지 않고 정상 실행상태를 유지하게 만들 수 있다.

예외의 종류

일반 예외 (Exception)

  • 자바소스 컴파일 과정에서 예외를 처리하는지 검사하고 컴파일 오류를 발생시키는 예외이다.

실행 예외 (RuntimeException)

  • 컴파일 과정에서는 예외처리 코드를 검사하지 않는 예외이다.

  • 프로그램 실행 도중 실행 예외가 발생하면, 해당 예외 클래스로 객체를 생성한다.

일반 예외와 실행 예외의 공통점

  • 예외 처리가 필요하다.

  • java.lang.Exception 클래스를 상속받는다.

일반 예외와 실행 예외의 차이점

  • 일반 예외는 Exception을 상속받는다.

  • 실행 예외는 ExceptionRuntimeException을 둘 다 상속받는다.

실행 예외의 처리

  • 자바 컴파일러의 체크 대상이 아니기 때문에, 개발자가 직접 경험에 의해 예외처리 코드를 작성해야 한다.

  • 처리하지 않으면, 예외 발생 시 프로그램이 종료된다.

NullPointerException

  • null 값을 갖는 참조 변수에 객체 접근 연산자인 .(도트)를 사용하면 발생한다.

  • 참조할 메모리 주소가 없는데 객체를 사용하려 해서 예외가 발생한다.

ArrayIndexOutOfBoundsException

  • 배열에서 인덱스 범위를 초과하여 사용할 경우 발생한다.

NumberFormatException

  • 잘못된 문자를 숫자로 파싱하려 할 때 발생한다.

Integer.parseInt("삼백"); // NumberFormatException 발생
Integer.parseInt("300a"); // NumberFormatException 발생

ClassCastException

  • 상속했던 부모 클래스로 타입을 캐스팅 하는 것 혹은 상속했던 인터페이스로 타입을 변경하는 것과 같은 정상적인 캐스팅이 아닌 비정상적인 캐스팅을 하면 발생한다.

  • ClassCastException을 발생시키지 않기 위해서는 해당 클래스가 X의 인스턴스인지 확인하는 instanceof X와 같은 연산자로 확인하는 것이 좋다.

예외 처리 코드

  • 프로그램에서 예외가 발생했을 때, 프로그램의 갑작스러운 종료를 막고 정상실행을 유지하도록 처리하는 코드를 예외처리 코드라고 한다.

  • 일반 예외가 발생하는 코드를 작성하면 자바 컴파일러는 예외 처리 코드 작성을 강제한다.

  • 예외처리 코드는 try-catch-finally를 이용한다.

    • finally는 예외처리와 상관없이 무조건 실행되는 코드 블럭이다.

      • try 혹은 catch에서 return문을 사용하더라도 항상 실행된다.

예외 종류에 따른 처리 코드

다중 catch

  • catch 블록은 단 하나만 쓸 수 있는 게 아니라, 상황에 따라 여러개를 겹쳐 쓸 수 있다.

  • catch 블록이 여러 개인 경우 여러 개의 catch 블록 중 하나만 실행된다.

  • catch의 예외는 더 적은 예외를 포괄하는 하위 클래스일수록 더 위로 와야 한다.

    • 이를테면 Exception은 모든 예외를 포함하기 때문에, Exception이 가장 위에 있는 경우 어떠한 다른 예외 catch 블록으로도 넘어갈 수 없게 된다.

try {

} catch(FirstException e) {

} catch(SecondException e) { 

}

멀티 catch

  • 자바7부터 하나의 catch 블록에서 여러 개의 예외처리가 가능한 멀티 catch 기능을 추가했다.

try {

} catch (FirstException | SecondException e) {

}

자동 리소스 닫기

  • 자바7부터 try-with-resources를 사용하면 예외 발생 여부와 상관없이 사용했던 리소스 객체의 .close() 메소드를 호출해서 안전하게 리소스를 닫아준다.

    • 리소스 객체의 종류로는 입출력 스트림, 서버 소켓, 소켓, 각종 채널 등이 있다.

단일 리소스인 경우

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

복수 리소스인 경우

try (FileInputStream fis = new FileInputStream("file1.txt");
     FileOutputStream fos = new FileOutputStream("file2.txt")) {
  // ...
} catch (Exception e) {
  // ...
}

자동 리소스 닫기가 가능한 조건

  • 리소스 클래스에서 AutoClosable이라는 인터페이스를 상속해야 한다.

public class ExampleResource implements AutoCloseable{
    @Override
    public void close() throws Exception {
        System.out.println("close method called");
    }
}
public class Main {
    public static void main(String[] args) {
        ExampleResource exampleResource = new ExampleResource();

        try(exampleResource) {
            System.out.println("try block");
        } catch(Exception e) {
            System.out.println("catch block");
        }
    }
}

위와 같이 ExampleResource 클래스와 Main 클래스를 구현하면,

실행 결과로 위와 같은 결과가 나온다.

AutoClosable인터페이스에서 오버라이드하여 구현한 .close() 메소드가 자동으로 호출되는 것을 볼 수 있다.

예외 떠넘기기

  • throws 키워드를 이용해 메소드에서 처리하지 않은 예외를 호출한 곳으로 떠넘길 수 있다.

void method() throws Exception {
  // 예외가 발생하면 호출한 곳으로 떠넘긴다.
}

위와 같이 throws를 통해 예외를 떠넘기는 메소드를 호출할 때는 해당 예외를 try ... catch문으로 처리하거나 또 한번 throws를 통해 예외를 떠넘기거나 해야 한다.

public class Main {
    public static void main(String[] args) {
        method();
    }

    public static void method() throws Exception {
        System.out.println("method called");
    }
}

위의 코드에서 컴파일러가 행동을 강제한다.

자바 API 도큐먼트에서 예외 표기

자바 API 도큐먼트 사이트에서는 위와 같이 throws로 해당 메소드가 어떤 예외를 던지는지 잘 표기되어 있다.

main 메소드에서의 throws

main 메소드에서 throws로 예외를 던져버리면, 사용자 입장에서는 갑자기 알 수 없는 메세지가 뜨며 프로그램이 종료되기 때문에 주의해야 한다.

사용자 정의 예외와 예외 발생

  • 개발자가 만든 애플리케이션 서비스와 관련된 예외 등을 개발자가 직접 만들 수 있다.

사용자 정의 예외 클래스 선언

  • 일반 예외는 Exception 클래스를 상속한다.

  • 실행 예외는 RuntimeException 클래스를 상속한다.

  • 사용자 정의 예외 클래스명은 Exception으로 끝내는 것이 관례이다.

  • 생성자 2개를 관례적으로 포함한다.

    • 아무런 내용이 없는 생성자

    • String 타입의 메세지를 받아 전달하기 위한 생성자

      • 메세지를 상위 클래스로 넘겨 catch 블록에서 이용할 수 있게 해준다.

public class XXXException extends [ Exception | RuntimeException ] {
  public XXXException() { }
  public XXXException(String message) { super(message); }
}

코드에서 예외를 고의로 발생시키는 방법

throw new XXXException();
throw new XXXException("메세지");

예외에 대한 정보 얻기

  • catch(Exception e) { ... } 블록에서 넘어온 Exception e 변수를 이용한다.

    • e.getMessage(): Exception 클래스로 넘어온 예외 메세지를 가져올 수 있다.

    • e.printStackTrace(): 예외 발생 코드를 추적해서 모두 콘솔에 출력한다. 어떤 예외가 어디에서 발생했는지 상세하게 출력해준다.

Last updated