본문 바로가기

JAVA,JSP

예외처리(1)

예외란 프로그램 실행 도중 발생하는 문제 상황을 얘기합니다.

따라서 컴파일 시 발생하는 문법적인 오류는 예외의 범주에 포함되지 않습니다.

예를 들면 아래와 같은 상황이 있습니다.

public static void main(String[] args){
    String str = null;
    System.out.println(str.length());
}

이 상황은 문법적으로는 문제가 없으므로, 컴파일 오류가 발생하지 않습니다.

그런데 실행하면

와 같이 Exception이 발생합니다.

str변수에서 length 메서드를 호출한건데, str에는 현재 null이 들어가 있으므로(주소값이 없으므로)

비어있는 주소를 참조하여 메서드를 실행하려 했다.. 뭐 이런의미로 NullPointerException이 발생한 겁니다.


근데 보다시피 해당 NullPointerException은 java.lang 패키지 내에 있는 클래스네요.

이렇듯 Exception라고 별 다른게 아니라 다 클래스들입니다. 예외적인 상황에 따라 대부분의 클래스가 정의되어 있고,

약속된 예외 상황에 따라 해당 예외 클래스가 발생합니다.


몇가지 예를 볼까요

NullPointerException : 참조변수가 null인 상황에서 메서드를 호출하는 상황

NegativeArraySizeException : 배열 선언 과정에서 배열의 크기를 음수로 지정하는 상황

ClassCastException : 허용되지 않는 형변환을 하는 상황

이렇듯 대부분 상황에 따라 정의되어 있습니다.



예외 클래스의 계층도

예외도 결국 클래스라고 했습니다. 그러므로 각 클래스간 계층이 존재하고, 이에 따라 예외의 종류도 나뉩니다.

출처 : http://masamune.tistory.com/23


보다시피 Throwable이 예외 클래스의 최상위 클래스입니다.

그리고 아래에 Error, Exception 2가지 클래스가 있는데, 이 2개의 클래스가 예외의 큰 범주입니다.


1) Error

단순히 예외라고 하기에는 심각한 오류의 상황을 표현하는 예외입니다.

위의 그림에서 OutOfMemoryError, StackOverflowError 등이 보이시죠? 다들 많이 보셨을겁니다.

한 단계 올라가보면 VirtualMachineError로 자바 가상머신에 문제가 생겼음을 알려주고 있습니다.

한 단계 더 올라가보면 Error 클래스가 보이지요. 이처럼 심각한 오류들은 다 Error 예외의 하위 예외로 정의되어 있습니다.

이건 뭐.. 저희가 할 수 있는 특별한 방법이 없으므로 그냥 프로그램이 종료되도록 놔두는 수밖에 없습니다.

종료 후에 원인을 찾고 해결하던가 해야합니다. 당장 애플리케이션 단에서는 저희가 조치를 취할 방법이 없습니다.


2) Exception

일반적으로 우리가 마주하게 되는 예외들로, 저희가 직접 처리할 수 있는 대부분의 예외들을 말합니다.

Exception 내에서도 또 종류가 2가지로 나뉘는데 그것은 2장에서 설명하겠습니다.



예외 처리 방법

예외처리의 대상이 되는 Exception 예외의 하위 예외를 대상으로 하며, 처리 방법에는 2가지가 있습니다.

1) try ~ catch [ ~finally ]

사용자가 직접 예외를 처리하는 방법입니다. 

public static void main(String[] args){
    try{
        String str1 = null;
        String str2 = "asd";
        
        System.out.println(str1.length());
        System.out.println(str2.length());
    } catch(NullPointerException e){
        System.out.println("참조변수의 값이 NULL 입니다.");
    } catch(Exception e){
        System.out.println("다른 예외가 발생하였습니다.");
    } finally{
        System.out.println("마지막에 항상 실행되는 부분입니다.");
    }
}

예외가 발생할 수 있는 부분을 try 구문으로 감싸고, 

예외가 발생 시 발생 시점으로 부터 더 이상 try 부분의 코드는 진행되지 않고 catch 구문으로 들어가게 됩니다.

catch의 파라미터는 try 구문에서 발생한 예외 클래스를 받게 됩니다. 상속관계도 가능합니다.

catch 구문은 1개 이상 정의 가능하며(하나의 로직에서 발생할 수 있는 예외는 1개 이상이기 때문에),

위에서 부터 순차적으로 실행됩니다.

하나의 catch 구문에 들어갔을 경우 그 아래 catch구문은 건너뛰게 됩니다. switch case 처럼요.

그리고 마지막 finally  구문예외가 발생하던, 발생하지 않던 언제나 실행하는 부분으로써 선택적인 사항입니다.


결과는 아래와 같습니다.


먼저 System.out.println(str1.length()); 부분에서 null값 참조로 NullPointerException이 발생하게 됩니다.

(예외가 발생했으므로 진행이 멈추게 되어 그 아래 System.out.println(str2.length()); 은 진행되지 않습니다.)

발생된 NullPointerException은 try 구문을 빠져나와 아래의 catch 구문에서 자기가 들어갈 곳을 찾게 됩니다.

위쪽부터 살펴보니 파라미터로 NullPointerException을 받는 catch 구문이 있네요. 

구문에 진입하고, 구문 내의 행위를 실행합니다. 

catch 구문의 선택은 switch case 와 같다고 했었죠.. NullPointerException 예외 처리 구문에 들어갔으니

아래의 Exception 예외 처리 구문에는 들어가지 않게 됩니다.


근데 여기서 유의하고 넘어가야 할 부분이 있습니다.

catch 구문의 위치를 바꾸었다면 어떻게 될까요?

예를 들어 아래와 같이 예외를 처리했을 경우를 보시죠.

catch(Exception e){

// Exception 예외 처리

} catch(IOException e){

// IOException 예외 처리

} catch(NullPointerException e){

// NullPointerException 예외 처리

}

Exception은 모든 예외의 상위 클래스이므로, 발생하는 예외를 모두 다 받을 수 있습니다. 상속관계도 가능하다고 했었죠.

즉, 이런식으로 예외처리 지정해버리면 백날 천날 예외 터져봐야 젤 위의 Exception 받는 부분에서 다 걸리므로

아래에 IOException, NullPointerException에 대한 예외 처리는 무용지물이 됩니다. 항상 유의해야 합니다.


마지막으로 finally 구문은 선택적인 부분입니다. 써도 그만 안 써도 그만.

try 구문이 정상적으로 끝나든, 예외가 발생해서 catch 구문에 들어가고 끝나든 언제든 실행 될 부분을 적어주면 됩니다.

예를 들면 자원 반납, 로깅 처리 등이 있습니다.


2) throws

throws는 던지다 라는 의미를 갖고 있습니다. 그리고 예외처리에서의 throws도 동일한 의미로 사용됩니다.

발생된 예외를 자신을 호출한 쪽으로 던져버리는 것입니다. 처리를 위임한다고 표현하면 되곘네요.

사용 예는 아래와 같습니다.

public static void main(String[] args){
    try{
        System.out.println(calculator('+',1,2));
        System.out.println(calculator('/',5,0));
        System.out.println(calculator('*',3,3));
    } catch(ArithmeticException e){
        System.out.println("0으로 나눌 수 없습니다.");
    }
}

static int calculator(char sign, int num1, int num2) throws ArithmeticException{
    int result = 0;
    switch(sign){
    case '+':
        result = num1 + num2;
        break;
    case '-':
        result = num1 - num2;
        break;
    case '*':
        result = num1 * num2;
        break;
    case '/':
        result = num1 / num2; // 0으로 나누는 경우가 발생할 수 있다
        break;
    }
    
    return result;
}

간단한 계산기 프로그램입니다.

보다시피 / 연산에서 0으로 나누는 예외인 ArithmeticException이 발생할 수 있지만 그에 대한 처리가 전혀 없습니다.

근데 잘 보시면 처리가 없는 대신 위에 throws 에서 ArithmeticException을 선언해주고 있네요.

위 구문의 의미는, ArithmeticException이 발생할 경우 자신을 호출한 메서드 쪽으로 그 처리를 던지겠다는 의미입니다.

위의 상황에서 calculator를 호출한 쪽은 main 메서드이므로, 보다시피 main 메서드에서 해당 예외를 잡아

처리하고 있음을 볼 수 있습니다.



JVM의 예외처리

main 메서드는 프로그램의 시작점이지만, main 메서드도 예외를 받았을 경우 throws로 던져버릴 수 있습니다.

그러면 결국 main을 호출한 쪽으로 던져지게 됩니다. main을 호출한 영역은 가상머신(JVM) 이죠.

결과적으로 예외처리가 가상머신에 의해 이루어지게 되는 것입니다.

가상머신의 예외처리 방식

1. getMessage 메서드를 호출한다.

2. printStackTrace 메서드를 호출해 예외상황이 발생해서 전달되는 과정을 출력해 준다.

3. 프로그램을 종료한다.


getMessage, printStackTrace 메서드는 예외의 최 상위 클래스인 Throwable에 정의된 메서드입니다.

getMessage는 예외에 대해 정의된 간단한 메시지를 보여주며, printStackTrace는 예외 발생 과정을 상세하게 출력해줍니다.


위의 calculator에서 Throwable 제공 메서드들을 사용해보겠습니다.

try{
    System.out.println(calculator('+',1,2));
    System.out.println(calculator('/',5,0));
    System.out.println(calculator('*',3,3));
} catch(ArithmeticException e){
    System.out.println(e.getMessage());
    e.printStackTrace();
}

결과는 아래와 같습니다.


getMessage는 간단하게 예외의 내용을 출력해주고, printStackTrace는 예외 발생 과정을 상세하게 출력해줍니다.

특히나 printStackTrace는 예외 발생 시 원인을 찾는데 상당한 도움이 됩니다.



나머지는 여기서!

게시글 바로가기 > 예외처리(2)

'JAVA,JSP' 카테고리의 다른 글

리플렉션(1), Class 클래스  (5) 2016.07.29
예외처리(2)  (0) 2016.07.03
java.lang.NoSuchMethodError  (1) 2016.06.28
동일성, 동등성  (4) 2016.06.24
Jad 설치 및 이클립스에 추가하기  (0) 2016.06.13