[Java] 다양한 예외처리 방법과 예외 정보 얻기, 다중 예외처리의 방법과 주의사항

예외 처리 코드

프로그램에서 예외가 발생했을 경우 프로그램의 갑작스러운 종료를 막고, 정상 실행을 유지할 수 있도록 처리하는 코드를 예외 처리 코드라고 한다. 자바 컴파일러는 소스 파일을 컴파일할 때 일반 예외가 발생할 가능성이 있는 코드를 발견하면 컴파일 오류를 발생시켜 개발자로 하여금 강제적으로 예외 처리 코드를 작성하도록 요구한다. 그러나 실행 예외는 컴파일러가 체크해주지 않기 떄문에 예외 처리 코드를 개발자의 경험을 바탕으로 작성해야 한다. 예외 처리 코드는 try-catch-finally 블록을 이용한다. try-catch-finally 블록은 생성자 내부와 메소드 내부에서 작성되어 일반 예외와 실행 예외가 발생할 경우 예외 처리를 할 수 있도록 해준다. try-catch-finally 블록 작성 방법은 다음과 같다.


try 블록에서는 예외 발생 가능 코드가 위치하고 catch 문은 예외 발생 시 초리하는 코드가 위치한다 만약 try 블록에서 예외가 발생하면 catch 코드를 실행하고 마지막으로 finally 코드를 실행한다. 만약 try 블록에서 예외가 발생 안하면 catch는 건너뛰고 fianlly 블록만 실행한다. 즉 finally 블록은 무조껀 실행되는 코드이다. finally 블록은 옵션이므로 생략 가능하다. 


이클립스는 일반 예외가 발생할 가능성이 있는 코드를 작성하면 빨간 밑줄을 그어 예외 처리코드의 필요성을 알려준다. 빨간 밑줄에 마우스를 가져다 놓으면 Unhandled exception(처리되지 않은 예외) 정보를 알 수 있다.




Class.forName() 메소드는 매개값으로 주어진 클래스가 존재하면 Class 객체를 리턴하지만, 존재하지 않으면 ClassNotFoundException 예외를 발생시킨다. ClassNotFOundException 예외는 일반 예외이므로 컴파일러는 개발자로 하여금 예외 처리 코드를 다음과 같이 작성하도록 요구한다. 


public class TryCatchFinallyExample {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("Java.lang.String2");
        } catch (ClassNotFoundException e) {
            System.out.println("클래스가 존재하지 않습니다.");
        }
    }
}
cs

java.lang.Stirng2 클래스가 존재하지 않기떄문에 예외 처리문을 실행한다.  이처럼 일반 예외는 컴파일러가 예외 처리 코드를 체크해주기 때문에 이클립스에서도 빨간 밑줄이 생겼다. 하지만 실행 예외는 오로지 개발자의 경험에 의해 예외 처리를 해주는 수밖에 없다.


public class TryCatchFinallyRuntimeExceptionExample {
    public static void main(String[] args) {
        String data1 = null;
        String data2 = null;
    
        try {
            data1 = args[0];
            data2 = args[1];
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("실행 매개값의 수가 부족합니다.");
        }
    }
}
cs


실행 예외는 개발자가 직접 try - catch 문을 작성해줘서 예외처리를 해줘야 한다. 컴파일러가 예외 처리 코드를 체크하지 않기 떄문이다.


try 블록 내부는 다양한 종류의 예외가 발생할 수 있다. 이 경우 발생되는 예외별로 예외 처리 코드를 다르게 하려면 다중 catch 블록을 작성해야 한다.


public class CatchByExceptionKindExample {
    public static void main(String[] args) {
        try {
            String data1 = args[0];
            String data2 = args[1];
            
            int value1 = Integer.parseInt(data1);
            int value2 = Integer.parseInt(data2);
            int result = value1 + value2;
            System.out.println(data1 + "+" + data2 + "=" + result);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("실행 매개값의 수가 부족합니다.");
        } catch (NumberFormatException e) {
            System.out.println("숫자로 변환할 수 없습니다.");
        } finally {
            System.out.println("다시 실행하세요");
        }
    }
}
cs

위 코드를 보면 catch문이 2개 있다. 하나는 ArrayIndexOutOfBoundsException 이므로 인덱스 범위가 넘어설 경우 이 예외문을 실행하고 다른 하나는 잘못된 문자열을 숫자로 변환하려고 할 때 예외문을 실행시킨다 즉 발생되는 예외별로 처리가 되는 것이다.  다중 catch를 사용할 떄 주의할 점은 상위 예외 클래스가 하위 예외 클래스보다 아래쪽에 위치해야 한다는 것이다. 아래 예제를 보자


public class CatchByExceptionKindExample {
    public static void main(String[] args) {
        try {
            String data1 = args[0];
            String data2 = args[1];
            
            int value1 = Integer.parseInt(data1);
            int value2 = Integer.parseInt(data2);
            int result = value1 + value2;
            System.out.println(data1 + "+" + data2 + "=" + result);
        } catch (Exception e) {
            System.out.println("실행 매개값의 수가 부족합니다.");
        } catch (NumberFormatException e) {
            System.out.println("숫자로 변환할 수 없습니다.");
        } finally {
            System.out.println("다시 실행하세요");
        }
    }
}
cs

Exception 클래스는 일반, 실행 예외클래스의 상위 클래스 이다.  이 코드는 오류가 나는데 그 이유는 NumberFormatExcption은 절대로 실행되지 않기 때문이다. 모든 예외 발생이 상위 클래스인 Exception 예외처리에 모두 걸리기 떄문이다. 그러므로 이 코드는 아래 코드처럼 바꿔야 한다.


public class CatchByExceptionKindExample {
    public static void main(String[] args) {
        try {
            String data1 = args[0];
            String data2 = args[1];
            
            int value1 = Integer.parseInt(data1);
            int value2 = Integer.parseInt(data2);
            int result = value1 + value2;
            System.out.println(data1 + "+" + data2 + "=" + result);
        } catch (NumberFormatException e) {
            System.out.println("숫자로 변환할 수 없습니다.");
        } catch (Exception e) {
            System.out.println("실행 매개값의 수가 부족합니다.");
        } finally {
            System.out.println("다시 실행하세요");
        }
    }
}
cs

상위 클래스인 Exception클래스를 아래로 내리는 것이다. 그러면 NumberFormatException 이외에 예외 발생은 모두 Exception 블록에서 처리 될것이다.



다중 catch


예외 떠넘기기

메소드 내부에서 예외가 발생할 수 있는 코드를 작성할 떄 try-catch 블록으로 예외를 처리하는 것이 기본이지만, 경우에 따라서는 메소드르 호출한 곳으로 예외를 떠넘길 수도 있다. 이때 사용하는 키워드가 throws이다. throws 키워드는 메소드 선언부 끝에 작성되어 메소드에서 처리하지 않은 예외를 호출한 곳으로 떠넘기는 역할을 한다. 다음 예제를 보자 


public class ThrowsExample {
    public static void main(String[] args) {
        try {
            findClass();
        } catch (ClassNotFoundException e) {
            System.out.println("클래스가 존재하지 않습니다.");
        }
    }
    
    public static void findClass() throws ClassNotFoundException {
        Class clazz = Class.forName("java.lang.String2");
    }
}

cs


findClass() 옆에 throws ClassNotFoundException이 있고 메소드문 안에는 try - catch 문이 존재하지 않는다 왜냐하면 throws로 예외 처리를 떠넘겨 버렸기 때문이다 이렇게 되면 자신을 호출한 main 메소드문 안에서 예외처리를 해줘야 한다. 이처럼 자신이 직접 예외를 처리하지 않고 던지는게 가능하다.


main() 메소드에서도 throws 키워드를 사용해서 예외를 떠넘길 수 있는데, 결국 JVM이 최종적으로 예외를 처리한다. JVM은 예외의 내용을 콘솔에 출력하는 것으로 예외 처리를 한다.

main() 메소드에서 thorws Exception()을 붙이는 것은 좋지 못한 예외 처리 방법이다. 프로그램 사용자는 프로그램이 알 수 없는 예외 내용을 출력하고 종료되는 것을 좋아하지 않는다. 그렇기 때문에 main()에서 try-catch 블록으로 예외를 최종 처리하는 것이 바람직하다.



예외 발생시키기

코드에서 예외를 발생시키는 방법이다.


public class ThrowsNewExcptionExample {
    static void prn(int i, int j) throws Exception  {
        if (j==0) {
            throw new Exception("0으로 나누기 오류 발생");
        }
    }
    
    public static void main(String[] args) {
        int i = 1;
        int j = 0;
        
        try {
            prn(i,j);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println("오류발생");
        }
    }
}
cs


prn() 메소드에 매개변수 j가 0이면 throw new Exception("0으로 나누기 오류 발생") 이라는 코드가 실행된다 즉 자기가 직접 오류를 발생시켜 자신을 호출한 곳에서 예외 처리를 시키도록 하는 것이다. "0으로 나누기 오류 발생" 출력은 e.getMessage()를 통해서 출력이 된것이다. 이것은 예외 정보를 얻은것인데 자세히 알아보도록 하자



예외 정보 얻기

try 블록에서 예외가 발생되면 예외 객체는 catch 블록의 매개 변수에서 참조하게 되므로 매개 변수를 이용하면 예외 객체의 정보를 알 수 있다. 모든 예외 객체는 Exception 클래스를 상속하기 때문에 Exception이 가지고 있는 메소드들을 모든 예외 객체에서 호출할 수 있다. 그 중에서도 가장 많이 사용되는 메소드는 getMessage()와  printStackTrace()이다. 예외를 발생시킬 떄 다음과 같이 String 타입의 메시지를 갖는 생성자를 이용하였다면, 메시지는 자동적으로 예외 객체 내부에 저장된다. 더 자세히 알아보자


getMessage()와 printStackTrace()

예외가 발생했을 떄 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨져 있으며, getMessage()와 printStackTrace()를 통해서 이 정보들을 얻을 수 있다. catch블럭의 괄호()세 선언된 참조변수를 통해 이 인스턴스에 접근할 수 있다. 이 참조변수는 선언된 catch블럭 내에서만 사용 가능하며, 자주 사용되는 메서드는 다음과 같다.


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

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


public class PrintStackTraceExample {
    public static void main(String[] args) {
        System.out.println(1);
        System.out.println(2);
        
        try {
            System.out.println(3);
            System.out.println(0/0);
            System.out.println(4);
        } catch (ArithmeticException e) {
            e.printStackTrace();
            System.out.println("예외메시지 : " + e.getMessage());
        }
        System.out.println(6);
    }
}
cs


위 예제의 결과는 예외가 발생해서 비정상적으로 종료되었을 때의 결과와 비슷하지만 예외는 catch문에 의해 처리되었으며 프로그램은 정상적으로 종료되었다. 그 대신 ArithmeticException인스턴스의 printStackTrace()를 사용해서, 호출스택(call stack)에 대한 정보와 예외 메시지를 출력하였다.

이 처럼 예외처리를 하여 예외가 발생해도 비정상적으로 종료하지 않도록 해주는 동시에, printStackTrace() 또는 getMessage()와 같은 메서드를 통해서 예외의 발생원인을 알 수 있다.




신용권, 이것이 자바다, 한빛미디어

남궁성, 자바의 정석, 도우 출판

댓글

Designed by JB FACTORY