Dico

[Java] 예외 처리 01

예외 처리 Exception Handling

프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것.

프로그램의 비정상 종료를 막고, 정상적인 실행 상태를 유지할 수 있도록 한다.

처리되지 못한 예외(Uncaught Exception)는 JVM의 예외처리기(UncaughtExceptionHandler)가 받아서 예외의 원인을 화면에 출력한다.

프로그램 오류

  • 컴파일러 에러 Compile-time Error

     컴파일 시에 발생하는 에러

     컴파일러가 소스코드(*.java)에 대해 오타나 잘못된 구문, 자료형 체크 등의 기본적인 검사를 수행하여 오류를 알려준다.

     컴파일을 성공적으로 마치고 나면, 클래스 파일(*.class)이 생성되고, 생성된 클래스 파일을 실행할 수 있게 된다.

  • 런타임 에러 Runtime Error

     실행 시에 발생하는 에러

     실행 중에 에러에 의해서 잘못된 결과를 얻거나 프로그램이 비정상적으로 종료되는 것.

     에러 Error :  메모리 부족(OutOfMemoryError), 스택오버플로우(StackOverflowError) 등 발생하면 복구할 수 없는 심각한 오류.

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

예외 클래스의 계층구조

실행 시 발생할 수 있는 오류(Error, Exception)를 클래스로 정의.

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

ExceptionRuntimeException
IOExceptionArithmeticException
DataFormatExceptionClassCastException
FileNotFoundExceptionNullPointerException
* RuntimeExceptionIndexOutOfBoundException
  • Exception 클래스들

     외부의 영향으로 발생하는 예외(프로그램 사용자의 실수 등)

     Exception 클래스와 그 자손 클래스들(RuntimeException 클래스들 제외)

      - 존재하지 않는 파일의 이름을 입력하거나(FileNotFoundException)

      - 실수로 클래스의 이름을 잘못 적었거나(ClassNotFoundException)

      - 입력한 데이터 형식이 잘못된(DataFormatException) 경우 등

  • RuntimeException 클래스들

     프로그래머의 실수로 발생하는 예외

     RuntimeException 클래스와 그 자손 클래스들

      - 배열의 범위를 벗어나거나(IndexOutOfBoundException)

      - 값이 null인 참조변수의 멤버를 호출하려 했거나(NullPointerException)

      - 클래스간의 형변환을 잘못하거나(ClassCastException)

      - 정수를 0으로 나누는(ArithmeticException) 등

예외 처리하기 try-catch문

발생한 예외의 종류와 일치하는 단 한 개의 catch블럭만 수행된다.

일치하는 catch 블럭이 없으면 예외는 처리되지 않는다.

블럭 내에 포함된 문장이 하나뿐이어도 괄호{}를 생략할 수 없다.

try {
    // 예외 발생 가능성이 있는 문장
} catch (Exception e) {
    // e가 발생했을 경우, 이를 처리하기 위한 문장
}

흐름

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

    발생한 예외와 일치하는 catch블럭이 있는 지 확인한다.

    일치하는 catch블록을 찾으면, 그 블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져 나와 그 다음 문장을 계속해서 수행한다.

    일치하는 catch블럭을 찾지 못하면, 예외는 처리되지 못한다.

    try블럭에서 예외가 발생한 위치 이후에 있는 문장들은 수행되지 않는다.

    try블럭에 포함시킬 코드의 범위를 잘 선택해야 한다.

public class Test {
    public static void main(String[] args) {

        System.out.println("0");                // 0
        try {
            System.out.println("1");            // 1
            System.out.println("error" + 2 / 0);
            System.out.println("3");
        } catch (Exception e) {
            System.out.println("4");            // 4
        }
        System.out.println("5");                // 5
    }
}
  • try블럭 내에서 예외가 발생하지 않은 경우

    catch블럭을 거치지 않고 전체 try-catch문을 빠져 나와 그 다음 문장을 계속해서 수행한다.

public class Test {
    public static void main(String[] args) {

        System.out.println("0");                // 0
        try {
            System.out.println("1");            // 1
        } catch (Exception e) {
            System.out.println("2");
        }
        System.out.println("3");                // 3
    }
}

catch블럭

괄호()와 블럭{} 두 부분으로 나눠져 있다.

괄호() 안에 처리하고자 하는 예외와 같은 타입의 참조변수를 하나 선언한다.

예외가 발생하면, 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다.

첫 번째 catch블럭부터 차례대로 내려가면서 참조변수의 종류와 생성된 예외 클래스의 인스턴스를 instanceof 연산자를 통해 검사한다.

Exception클래스 타입의 참조변수를 선언하면 어떤 종류의 예외가 발생하더라도 그 catch블럭에 의해 처리되도록 할 수 있다.

public class Test {
    public static void main(String[] args) {

        System.out.println("0");                         // 0
        try {
            System.out.println("1");                     // 1
            System.out.println("error" + 2 / 0);
            System.out.println("3");
        } catch (ArithmeticException e) {
            System.out.println("ArithmeticException");   // ArithmeticException
            if(e instanceof ArithmeticException) {
                System.out.println("true");              // true
            }
        } catch (Exception e) {
            System.out.println("Exception");
        }
        System.out.println("4");                         // 4
    }
}

예외가 발생했을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨져 있다.

예외가 여러 개 발생했을 경우 마지막 예외에 대한 내용만 출력된다.

  • printStackTrace()

    예외 발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.

  • getMessage()

     발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.

public class Test {
    public static void main(String[] args) {

        System.out.println("0");                      // 0
        try {
            System.out.println("1");                  // 1
            System.out.println("error" + 2 / 0);
            System.out.println("3");
        } catch (ArithmeticException e) {
            System.out.println(e.getMessage());       // / by zero
            e.printStackTrace();                      // java.lang.ArithmeticException: / by zero at Test.main(Test.java:7)
        }
        System.out.println("4");                      // 4
    }
}
  • 멀티 catch블럭

     JDK1.7부터 여러 catch블럭을 '|' 기호를 이용하여 하나의 catch블럭으로 합칠 수 있다.

     개수에 제한이 없다.

     멀티 catch블럭에 선언된 참조변수는 상수다.

     두 예외 클래스가 조상과 자손의 관계에 있다면, 조상 클래스만 써주는 것과 똑같기 때문에 컴파일 에러가 발생한다.

try {
    ...
} catch (ParentException | ChildException e) {    // error
    ...
}
try {
    ...
} catch (ParentException e) {
    ...
}

finally블럭

try-catch문과 함께 예외의 발생여부에 상관없이 실행되어야 할 코드를 포함시킬 목적으로 사용한다.

public class Test {
    public static void main(String[] args) {

        System.out.println("0");                         // 0
        try {
            System.out.println("1");                     // 1
            System.out.println("error" + 2 / 0);
            System.out.println("3");
        } catch (ArithmeticException e) {
            System.out.println("4");                     // 4
        } finally {
            System.out.println("5");                     // 5
        }
        System.out.println("6");                         // 6
    }
}

return문을 만나도 finally블럭의 문장들은 수행된다.

public class Test {
    public static void main(String[] args) {

        System.out.println("0");                         // 0
        try {
            System.out.println("1");                     // 1
            return;
        } catch (ArithmeticException e) {
            System.out.println("3");
        } finally {
            System.out.println("4");                     // 4
        }
        System.out.println("5");
    }
}

참고 서적: 자바의 정석 3판