티스토리 뷰

Programming/Java

자바 프로그래밍 - 9

Mongi0417 2023. 6. 20. 23:07

9.1 예외 처리의 기초

finally 블록

try-catch 구조 다음에 finally 블록을 추가할 수 있다.

 

자바의 try-catch-finally 구문의 구조와 동작은 아래와 같다.

1) try 블록을 실행하다 예외가 발생하면 try 블록의 실행이 중단되고 catch 블록을 차례대로 검사한다. 예외 타입이 일치하는 catch 블록을 발견하면 해당 catch 블록을 실행하고 마짐가으로 finally 블록을 실행한 후 나머지 프로그램의 실행이 계속된다.

2) try 블록을 실행하다가 예외가 발생했지만 매개변수의 타입이 일치하는 catch 블록을 발견할 수 없는 경우에는 finally 블록을 실행하고 나서 오류 메시지를 출력하고 프로그램이 강제 종료된다.

3) try 블록의 실행에서 예외가 발생하지 않으면 catch 블록은 모두 무시되고 finally 블록이 실행된 후 나머지 프로그램의 실행이 계속된다.

 

finally 블록이 굳이 필요한 이유는?

2)와 같이 예외가 발생했는데 예외 타입이 일치하는 catch 블록이 없는 경우를 생각해 보면 알 수 있다. finally 블록에 들어 있는 코드는 예외 처리가 실패하여 프로그램이 강제 종료되는 경우에도 반드시 실행된다.

결과적으로, 프로그램이 예외로 인해 강제 종료되더라도 사용한 자원을 반납하는 코드가 실행되도록 보장하려면 finally 블록 내에 작성해야 하는 것이다.

 

9.2 예외의 종류와 예외 처리

확인 예외와 비확인 예외

자바에서 예외는 크게 두 종류로 나눌 수 있다. try-catch로 잡아서 처리하지 않아도 되는 ArithmeticException, ArrayIndexOutOfBoundsException 등이 예외를 비확인 예외라 부르고, IOException과 같이 반드시 잡아서 처리해야 하는 예외를 확인 예외라고 부른다. 이러한 예외들은 모두 자바가 제공하는 미리 정의된 클래스이며 Exception 클래스의 자손 클래스이다.

 

자바의 예외 클래스 계층

자바의 모든 예외는 Exception 클래스로부터 파생된다. RuntimeException으로부터 파생되는 모든 예외 클래스는 비확인 예외이며 비확인 예외를 실행 예외라고 부르기도 한다. 비확인 예외는 try-catch로 처리하지 않아도 컴파일 에러가 나지 않는다. 이런 예외는 try-catch로 처리하는 것이 아니라 예외의 원인이 발생하지 않도록 코드를 주의 깊게 작성하는 것이 원칙이다.

 

RuntimeException으로부터 파생되지 않은 모든 예외 클래스들은 확인 예외에 속하며, 프로그램에서 반드시 잡아서 처리해야 하는 예외이다.

 

Error 클래스

자바에서 에러는 Error 클래스의 객체라는 특정한 의미를 가진다. 자바 시스템이 특정한 종류의 심각한 문제에 직면하면 에러가 발생한다.

 

Error와 Exception 클래스 둘 다 Throwable 클래스의 서브 클래스이다. 엄밀히 말하면 에러와 예외는 서로 다른 클래스의 객체로 별개의 것이지만 에러는 비확인 예외와 비슷한 점이 있다. 즉, 비확인 예외와 마찬가지로 원한다면 catch 블록으로 잡을 수는 있지만 반드시 처리해야 하는 것은 아니다. 에러는 프로그래머가 제어할 수 없는 비정상적인 상황에서 발생한다. 메모리 소진 또한 그러한 비정상적인 상황의 하나이다. 이런 상황에서는 catch 블록이 아무런 도움이 되지 않으므로 에러를 처리하는 catch 블록을 작성하지 말아야 한다.

 

throws와 예외 선언

예외를 처리하는 방법에는 2가지가 있다. 첫 번째는 예외가 발생할 가능성이 있는 코드를 try-catch 구조로 감싸서 예외를 잡고 처리하는 것이다. 두 번째는 예외가 발생한 메소드에서 직접 예외를 잡아 처리하지 않고 다른 메소드에게 예외의 처리를 넘기는 것이다.

public class ExceptionDemo9 {
    public static void main(String[] args) {
        try {
            System.out.println("입력한 문자 = " + readOneChar());
        } catch (IOException e) {
            System.out.println("IOException 발생: " + e);
        }
    }

    public static char readOneChar() throws IOException {
        System.out.print("문자를 입력하시오: ");
        return (char) System.in.read();
    }
}

InputStream의 read 메소드가 확인 예외인 IOException을 발생시킬 가능성이 있다. 위의 코드에서는 두 번째 방법으로 main에게 예외의 처리를 떠넘기기 위해 readOneChar 메소드 헤딩에 키워드 throws와 예외의 목록을 작성하였다. 이를, 예외 선언이라고 한다. 여러 개의 예외가 발생할 가능성이 있다면 예외를 콤마로 구분하여 나열하는 것도 가능한다.

public void someMethod() throws ExceptionType1, ExceptionType2 {

예외 선언은 해당 메소드를 호출하면 어떤 예외가 발생할 가능성이 있는지 알려주는 역할을 한다. 이러한 방식으로 메소드 헤딩에 예외를 선언해 두면 메소드 내에서는 예외를 처리할 필요가 없으며 해당 메소드를 호출하는 메소드가 예외를 처리해야 한다.

 

예외 처리를 넘겨 받은 메소드 역시 예외를 선언하여 자신이 호출한 메소드에게로 예외의 처리를 다시 넘길 수 있다. 이러한 방식으로 메소드 호출 체인을 따라 계속 예외를 넘길 수 있지만, 최종적으로 catch 블록으로 예외를 처리하는 메소드가 있어야 한다. 이를 처리하지 않고 계속 넘기기만 한다면 프로그램이 강제 종료된다.

 

예외가 발생한 메소드에서 예외를 처리할지 예외를 선언하여 상위 메소드로 넘길지는 프로그램의 상황에 따라 결정된다. 예외가 발생한 메소드가 아니라 그 메소드를 호출한 메소드가 대응할 방법을 결정할 수 있는 상황이라면 예외를 선언하여 넘겨야 한다. InputStream의 read 메소드는 실행 중에 예외를 발생시킬 가능성이 있지만 해당 예외를 직접 처리하지 않고 예외를 선언하여 예외 발생 가능성을 경고하기만 하며, read 메소드를 사용하는 사용자 코드에서 예외를 처리하도록 한다. 자바의 많은 메소드들이 이와 같이 작성되어 있다.

 

사용자 정의 예외 클래스

필요에 따라 사용자는 새로운 예외 클래스를 정의하여 사용하는 것이 가능하다. 하지만 사용자가 정의하는 예외 클래스는 반드시 Exception 클래스의 자손 클래스가 되어야 한다. 이는 새로 정의되는 예외 클래스는 기존의 예외 클래스로부터 파생되어야 한다는 것이다.

public class ExceptionDemo10 {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("점수 합계는? ");
        int sum = input.nextInt();
        System.out.print("과목의 수는? ");
        int count = input.nextInt();

        try {
            if (count == 0)
                throw new DivideByZeroException();
            int average = sum / count;
            System.out.println("평균값 = " + average);
        } catch (DivideByZeroException e) {
            System.out.println(e);
        }
    }
}

class DivideByZeroException extends Exception {
    public DivideByZeroException() {
        super("0으로 나눌 수 없음!");
    }
}
점수 합계는? 200
과목의 수는? 0
DivideByZeroException: 0으로 나눌 수 없음!

 

9.3 프로그래밍 오류와 디버깅

프로그래밍 오류의 종류

프로그램의 오류는 세 가지 종류로 나누어 볼 수 있다.

1) 문법 오류(syntax error): 프로그램이 자바 문법에 맞지 않아 컴파일러에 의해 발생하는 오류

2) 실행 오류(runtime error): 프로그램의 실행 중에 비정상적인 상황을 초래하는 오류

3) 논리 오류(logical error): 프로그램이 중단 없이 정상적으로 실행되지만 잘못된 결과를 만들어내는 오류

 

문법 오류

문법 오류란 컴파일러가 찾아 주는 문법 상의 오류를 말한다. 예를 들어 클래스나 변수 이름을 잘못 쓰거나 문장 끝의 세미콜론을 빠뜨리는 등의 상황에 컴파일 에러가 발생한다. 문법 오류는 컴파일러가 추정한 에러의 위치와 원인이 명확히 드러나므로 찾아서 수정하기가 가장 쉬운 종류의 오류에 속한다.

 

실행 오류

문법 오류를 다 수정한 뒤 프로그램을 실행할 수 있는데, 프로그램 실행 중에 발생하는 비정상적이거나 잘못된 상황은 실행 오류에 속한다. 예외와 에러가 실행 오류에 속한다. 확인 예외에 속하는 예외들은 예외가 발생할 가능성이 있다면 반드시 catch 블럭을 작성해야 컴파일 되도록 정해져 있다.

 

비확인 예외가 발생하면 프로그램이 강제 종료되는데, 대응책은 에러의 경우와 마찬가지이다. 즉, catch 블록을 작성하는 것이 아니라 예외의 발생 원인을 찾아 프로그램을 수정하는 것이 원칙이다.

 

논리 오류

논리 오류는 프로그램이 컴파일 오류도 없고 예외나 에러 없이 정상적으로 실행되지만 잘못된 결과가 나오는 경우이다. 즉, 알고리즘을 잘못 세웠거나 프로그램에 논리적인 오류가 있다는 의미이다. 예를 들어, 합을 구하는 프로그램에서 덧셈 연산자 대신 뺄셈 연산자를 쓰는 경우이다. 논리 오류 때문에 실행 오류가 발생하여 에러 메시지가 나올 수도 있지만 그렇지 않고 프로그램이 정상적으로 실행되는 경우도 많다. 이러한 종류의 오류는 오류의 원인을 추정할 만한 힌트가 주어지지 않기 때문에 해결하기가 가장 어려운 종류의 오류이다.

 

프로그램이 논리 오류로 인해 기대한 결과를 내지 않는다면 어떻게 해야 할까? 다양한 입력을 넣어 실행해 보는 테스팅 과정과 프로그램의 실행을 단계별로 추적하는 디버깅 과정 또는 프로그램의 여러 위치에서 print 문으로 메시지를 출력하여 각 단계에서 일어나는 일을 추적해 보는 기법 등이 있다.

'Programming > Java' 카테고리의 다른 글

자바 프로그래밍 - 8  (0) 2023.06.20
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함