Programming Language/Java

[Java] 13. 예외 처리(Exception handling)(2) - try-catch 문

lumana 2024. 7. 9. 05:38

예외 처리(2): try-catch문


try-catch문

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

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

      • 발생한 예외의 종류와 일치하는 catch블럭이 없으면 예외는 처리되지 않는다.

아무 일도 하지 않는 예제

class ExceptionEx1 {
    public static void main(String[] args) {
        try {
            try { } catch (Exception e) { }
        } catch (Exception e) {
            try { } catch (Exception e) { }    // 컴파일 에러. 변수 e가 중복 선언되었다.
        }    // try-catch의 끝

        try {
        } catch (Exception e) {
        }    // try-catch의 끝
    }    // main의 끝
}
  • 하나의 메서드 내에 여러 개의 try-catch문이 사용될 수 있으며, try블럭 또는 catch블럭에 또 다른 try-catch 문이 포함될 수 있다.

    • catch블럭 내에서도 예외가 발생할 수 있기 때문

    • catch블럭 내에 또 하나의 try-catch문을 포함하는 경우 같은 이름의 참조변수를 사용할 수 없다. (구별이 안되니까)

0으로 나누는 케이스를 예외처리 하기

class ExceptionEx2 {
    public static void main(String args[]) {
        int number = 100;
        int result = 0;

        for(int i=0; i<10; i++) {
            result = number / (int)(Math.random() * 10);    // 7번째 라인
            System.out.println(result);
        }
    }    // main의 끝
}
  • 랜덤 값이 0이 나오면 예외가 발생해서 프로그램이 비정상적으로 종료된다.

    • 참고) 정수는 0으로 나눌 수 없지만, 실수를 0으로 나누는 것은 금지되어있지 않으며 예외가 발생하지 않는다.
  • 예외처리 구문을 추가해서 프로그램 실행 도중 비정상적으로 종료되지 않도록 수정해보자.

class ExceptionEx3 {
    public static void main(String args[]) {
        int number = 100;
        int result = 0;

        for(int i=0; i<10; i++) {
            try {
                result = number / (int)(Math.random() * 10);
                System.out.println(result);
            } catch (ArithmeticException e) {
                System.out.println("0");
            }    // try-catch의 끝
        }    // for의 끝
    }    // main의 끝
}
  • 랜덤 값이 0이 나오더라도, 프로그램이 종료되지 않고 for문의 다음 반복을 계속해서 수행하여 프로그램이 정상적으로 종료된다.

try-catch문에서 흐름

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

Example 8.5

class ExceptionEx5 {
    public static void main(String args[]) {
        System.out.println(1);
        System.out.println(2);
        try {
            System.out.println(3);
            System.out.println(0/0);    // 고의로 ArithmeticcException 발생시킴
            System.out.println(4);    // 실행되지 않는다.
        } catch (ArithmeticException e) {
            System.out.println(5);
        }    // try-catch의 끝
        System.out.println(6);
    }    // main메서드의 끝
}
  • System.out.println(0/0)에서 Exception이 발생하면

    • try블록을 바로 벗어나서 예외에 해당하는 catch블럭으로 이동한다
    • System.out.println(4)는 실행되지 않는다.
  • 예외가 발생할 위치 이후에 있는 try블럭의 문장들은 수행되지 않으므로, try블럭에 포함시킬 코드의 범위를 잘 선택해야 한다.

예외의 발생과 catch블럭

  • catch블럭의 괄호()내에는 처리하고자 하는 예외와 같은 타입의 참조변수 하나를 선언해야 한다.

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

  • 첫 번째 catch블럭부터 차례로 내려가면서 catch블럭의 괄호()내에 선언된 참조변수의 종류와 생성된 예외클래스의 인스턴스에 instanceof 연산자를 이용해서 검사하게 된다

    • 검사결과가 true일 때 까지 쭉 내려간다

    • 검사결과가 true인 catch블럭이 하나도 없으면 예외는 처리되지 않는다

  • 모든 예외 클래스는 Exception클래스의 자손이므로, catch블럭의 괄호()에 Exception클래스 타입의 참조변수를 선언해 놓으면 어떤 종류의 예외가 발생하더라도 이 catch블럭에 의해서 처리된다

Example 8.7

class ExceptionEx7 {
    public static void main(String args[]) {
        System.out.println(1);
        System.out.println(2);
        try {
            System.out.println(3);
            System.out.println(0/0);    // 고의로 ArithmeticcException 발생시킴
            System.out.println(4);    // 실행되지 않는다.
        } catch (ArithmeticException ae) {
            if (ae instanceof ArithmeticException)
                System.out.println("true");
            System.out.println("ArithmeticException");
        } catch (Exception e) {
            System.out.println("Exception");    // ArithmeticException을 제외한 모든 예외 처리
        }    // try-catch의 끝
        System.out.println(6);
    }    // main메서드의 끝
}
  • 첫 번째 검사에서 일치하는 catch블록을 찾았기 때문에 두 번째 catch블록은 검사하지 않는다

  • try-catch문의 마지막에 Exception 클래스 타입의 참조변수를 선언한 catch블록을 사용하면, 어떤 종류의 예외가 발생하더라도 이 catch블럭에 의해 처리되도록 할 수 있다.

printStackTrace()와 getMessage

  • 예외가 발생했을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨져 있으며, getMessage()와 printStackTrace()를 통해서 이 정보들을 얻을 수 있다.

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

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

Example 8.8

class ExceptionEx8 {
    public static void main(String args[]) {
        System.out.println(1);
        System.out.println(2);
        try {
            System.out.println(3);
            System.out.println(0/0);    // 고의로 ArithmeticcException 발생시킴
            System.out.println(4);    // 실행되지 않는다.
        } catch (ArithmeticException ae) {
            ae.printStackTrace();
            System.out.println("예외메시지 : " + ae.getMessage());
        }    // try-catch의 끝
        System.out.println(6);    // 6 출력됨 = 프로그램 정상 종료됨
    }    // main메서드의 끝
}
  • 실행 결과가 예외가 발생해서 비정상적으로 종료되었을 때와 비슷하지만 try-catch문에 의해 예외는 처리되었고 프로그램은 정상적으로 종료되었다.

  • 이처럼 try-catch로 비정상종료를 막아주는 동시에 위 메서드들을 예외의 발생 원인을 알 수 있다.

멀티 catch 블럭

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

    • '|' 기호로 연결할 수 있는 예외 클래스의 개수에는 제한이 없다.

    • 중복된 코드를 줄이기 위함이다.

    • 주의) 멀티 catch블록에 사용되는 '|'는 논리 연산자가 아니라 기호이다.

try {
        ...
} catch (ExceptionA e) {
        e.printStackTrace();
} catch (ExceptionB e2) {
        e2.printStackTrace();
}
  • 위 코드를 아래로 나타낼 수 있다.
try {
        ...
} catch (ExceptionA | ExceptionB e) {
        e.printStackTrace();
} 
  • 멀티 catch블럭의 '|' 기호로 연결된 예외 클래스가 조상과 자손의 관계에 있다면 파일 에러가 발생한다

    • instanceof로 타입을 검사하는데 조상과 자손관계 클래스가 연결되있을 이유가 없다.

    • 불필요한 코드를 제거하라는 의미에서 에러가 발생하는 것

  • 그리고 멀티 catch는 하나의 catch블록으로 여러 예외를 처리하는 것이기 때문에, 발생한 예외를 멀티 catch블럭으로 처리하게 되었을 때 블럭 내에서는 실제로 어떤 예외가 발생한 것인지를 알 수가 없다.

    • 그래서 ()에 존재하는 예외 클래스들의 공통 분모인 조상 예외 클래스에 선언된 멤버만(공통적으로 존재하는 것만) 사용할 수 있다.

    • instanceof를 쓰면 되는 거 아니냐고 물어본다면, instanceof로 분기를 나눌거면 애초에 멀티 catch를 사용할 이유가 없다.

  • 멀티 catch블럭에 선언된 참조변수 e는 상ㅇ수이므로 값을 변경할 수 없다는 제약이 있다.

    • 실제로 참조변수의 값을 바꿀일은 없다.
    • 멀티 catch블럭은 위에서 말했듯이 불필요한 코드를 제거하기 위함이므로 너무 이런 제약사항에 고민하지 않아도 된다.

예외 발생시키기

  • throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있다.
  1. 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
  2. 키워드 throw를 이용해서 예외를 발생시킨다.
class ExceptionEx9 {
    public static void main(String args[]) {
        try {
            Exception e = new Exception("고의로 발생시켰음");
            throw e;    // 예외를 발생시킴
            // 위의 두 줄을 다음과 같이 한 줄로 줄여쓸 수 있음
//            throw new Exception("고의로 발생시켰음");
        } catch (Exception e) {
            System.out.println("에러메시지 : " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("프로그램이 정상 종료되었음");
    }
}
  • Exception 인스턴스를 생성할 때 생성자에 String을 넣어주면 이 String이 Exception 인스턴스에 메시지로 저장되고, 이 메시지를 getMessage를 통해서 얻을 수 있다.
class ExceptionEx10 {
    public static void main(String[] args) {
        throw new Exception();    // Exception을 고의로 발생시킴
    }
}
  • 위 코드에서 예외처리가 되어야 할 부분에 예외처리가 되어 있지 않아 컴파일 에러가 발생한다.

    • Exception클래스들이 발생할 가능성이 있는 문장들에 대해 예외처리를 해주지 않으면 컴파일조차 되지 않는다
class ExceptionEx11 {
    public static void main(String[] args) {
        throw new RuntimeException();    // RuntimeException을 고의로 발생시킴
    }
}
  • RuntimeException클래스들과 그 자손 클래스에 해당하는 예외는 프로그래머의 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않는다

  • 컴파일러가 예외처리를 확인하지 않는 RuntimeException클래스들은 'unchecked exception'이라고 부르고

  • 예외처리를 확인하는 Exception클래스들은 'checked exception'라고 부른다

참조) Java의 정석 3rd edition(남궁성, 도우출판)