7.3.1 에러처리
보통의 프로그램에서는 finally를 잘 사용하지 않는 경향이 있지만, finally를 잘 사용하면 아주 논리적인 프로그램을 만들 수 있습니다. 이 절에서는 finally와 함께 사용되는 try, catch에 대해서 좀 더 자세히 알아보도록 하겠습니다.
일반적으로 에러를 다루는 방법을 다음과 같이 구분해서 생각해 볼 수 있습니다. 그 방법은 아래와 같습니다.
▣ 에러처리에서 학습할 내용
◈ 사용자가 필요하다고 생각해서 에러처리 구문을 사용하는 경우
◈ 사용자가 직접 에러 이벤트를 발생시키는 경우(throw)
◈ 다단계 catch
◈ 반드시 에러처리를 해주어야 하는 경우
◈ 에러처리 미루기(throws)
일단 필요하다고 생각해서 에러처리 루틴을 사용자가 직접 붙여주는 경우를 예제로 살펴보겠습니다.
『chap07\BasicException.java』
ⓙ───────────────────────────────────────
/**
사용자가 필요하다고 생각해서 에러처리 구문을 사용하는 경우
**/
public class BasicException {
public static void main(String args[]) {
try{
int[] ar = new int[]{0, 100, 200, 300};
//고위로 에러유발 : 배열의 범위를 벗어나도록 한다.
for(int i=0; i<ar.length+1; i++){
System.out.println("ar["+i+"]=" + ar[i]);
}
}catch(ArrayIndexOutOfBoundsException e) {
System.out.println("e.getMessage(): " + e.getMessage());
System.out.println("e.toString(): " + e.toString());
e.printStackTrace();
return;
}finally{
System.out.println("finally: 결국이리로 오는군요");
}
} //end of main
} //end of BasicException class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac BasicException.java
C:\javasrc\chap07>java BasicException
ar[0]=0
ar[1]=100
ar[2]=200
ar[3]=300
e.getMessage(): 4
e.toString(): java.lang.ArrayIndexOutOfBoundsException: 4
java.lang.ArrayIndexOutOfBoundsException: 4
at BasicException.main(BasicException.java:10)
finally: 결국이리로 오는군요
***/
───────────────────────────────────────ⓑ
위의 예제에서 고의로 에러를 발생시키고 있습니다. 선언된 배열의 크기를 벗어난 상태에서 출력을 했기 때문에 에러가 발생합니다. 배열의 범위를 벗어나도록 만든 부분은 다음과 같습니다.
▣ 배열의 범위를 벗어나도록 처리
◈ for(int i=0; i<ar.length+1; i++){
◈ System.out.println("ar["+i+"]=" + ar[i]);
◈ }
ar.length+1보다 작을 때까지 출력을 하려 했기 때문에 에러가 발생하는 것입니다. 존재하지 않는 ar[4]를 출력하는 것은 에러에 해당합니다. 배열의 범위를 벗어났기 때문에 프로그램상에서 ArrayIndexOutOfBoundsException 이벤트가 발생하게 됩니다. 이름이 좀 길죠.
ArrayIndexOutOfBoundsException은 컴파일할 때 점검하는 에러는 아닙니다. ArrayIndexOutOfBoundsException에 대한 에러처리 루틴은 선택적으로 사용할 수 있습니다. try 블록에서 ArrayIndexOutOfBoundsException 에러 이벤트가 발생한다면 위의 결과와 같이 catch가 이 이벤트를 포착합니다. catch는 이 이벤트를 처리하기 위해서 catch의 매개변수로 ArrayIndexOutOfBoundsException를 포함하고 있어야 합니다.
▣ 발생한 에러 이벤트와 catch의 매개변수
◈ catch에서 에러를 처리하기 위해서는 try에서 발생한 이벤트와 catch의 매개변수가 동일 계열이어야 한다.
위의 예제에서도 try에서 발생한 에러 이벤트와 catch에서 처리하려는 이벤트가 동일하기 때문에 catch 블록이 실행되는 것입니다. 매개변수로 넘어오는 e는 에러 이벤트에 관련된 모든 정보를 담고 있습니다.
▣ catch의 매개변수
◈ catch(ArrayIndexOutOfBoundsException e)
그런데 만약 catch의 이벤트가 ArrayIndexOutOfBoundsException이 아니 다른 에러 이벤트라면 어떻게 될까요? 이 때는 에러 이벤트가 catch로 가지 않고 가상머신으로 넘어가게 됩니다. 가상머신에서는 배열의 인덱스 에러를 감지하고 이에 대한 처리를 하게 될 것입니다. 이 때 가상머신은 프로그램을 중지시키게 됩니다.
catch 구문을 한번 보도록 하겠습니다. catch는 보통 에러의 정보 출력이나 에러에 대한 처리를 위한 코드가 들어가게 됩니다. 위의 예제에서는 에러 이벤트의 정보를 출력하는 방법을 보여주고 있습니다.
▣ 에러의 정보 출력 메서드
◈ e.getMessage() : 에러 이벤트와 함께 들어오는 메시지를 출력한다.
◈ e.toString() : 에러 이벤트의 toString()을 호출해서 간단한 에러 메시지를 확인한다.
◈ e.printStackTrace() : 에러 메시지의 발생 근원지를 찾아서 단계별 에러를 출력한다.
에러 이벤트의 멤버 메서드들을 이용해서 어떤 에러가 발생했는 지를 점검하게 됩니다. getMessage() 메서드는 에러가 메시지를 포함하고 있다면, 해당 메시지를 문자열 형태로 얻어내는 기능을 합니다. getMessage() 메서드의 실제 예는 다음 절의 예제에서 살펴보도록 하겠습니다. 그리고 toString()은 이벤트 자체를 출력하는 역할을 담당합니다. 보통의 경우 에러 이벤트의 toString()은 어떠한 에러가 발생했는 지를 문자열 형태로 넘겨주게 됩니다. 마지막으로 printStackTrace()는 에러가 어디서 발생했는지 스택을 추적(Tracing)하면서 단계별로 에러의 정보를 출력하는 메서드입니다.
하나만 더 살펴보고 넘어가도록 하죠. 아무리 return을 하더라도 finally 만큼은 실행을 하고 난 후에 메서드가 종료하게 됩니다. finally를 사용하는 가장 좋은 예는 데이터베이스에서 찾아 볼 수 있습니다. 데이터베이스에 Connection을 연결하고 작업을 하던 도중 에러가 발생했다고 가정하죠. 에러가 발생하더라도 Connection은 무조건 닫아주고 종료해야 합니다. 그렇다면 Connection 해제를 finally에 넣는 것이 좋지 않을까요? 프로그램 중간에 오류가 발생하면 데이터베이스의 Connection을 닫을 방법이 없을지도 모릅니다. 혹자는 다음과 같이 생각해 볼 수도 있습니다.
▣ 질문
◈ catch에서 Connection을 닫아주면 되지 않습니까?
이것도 약간의 문제는 있습니다. 만약 catch의 이벤트 처리 구문이 하나가 아니고 5개정도 된다면 5군데 모두 Connection을 닫아주는 구문을 사용해야 합니다. 이러한 경우 finally는 좋은 해답을 제시합니다. 에러가 나든 나지 않든 반드시 한번은 finally를 실행해야 하기 때문에 프로그램 실행시 반드시 필요한 구문은 finally 블록에 위치시키면 됩니다.
▣ finally 블록
◈ 에러가 나든 나지 않든 무조건 실행되는 블록