7.3 에러처리 실례

JAVA PROGRAMMING/JAVA 2010/02/21 21:32 by 킨테리 KinTeL



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 블록
◈ 에러가 나든 나지 않든 무조건 실행되는 블록



7.3.2 throw



다음으로 사용자가 직접 에러 이벤트를 발생시키는 경우입니다. 보통의 경우 try에서 에러가 감지되었을 때 자동으로 이벤트가 발생합니다. 이와 반대로 사용자가 직접 에러 이벤트를 생성한 후 이 이벤트를 발생시킬 수도 있습니다. 이 때 사용하는 키워드가 throw입니다. 다음과 같이 에러 이벤트를 하나 만든 후 throw를 이용해서 사용자가 만든 에러 이벤트를 발생시키면 됩니다.

▣ 수동으로 사용자가 직접 에러 이벤트 발생시키기
◈ throw new Exception();

수동으로 발생한 에러 이벤트라 하더라도 try, catch에서 처리되는 루틴은 동일합니다. 이벤트가 자동으로 발생하든 수동으로 발생하든 일단은 에러 이벤트가 발생하는 것이기 때문에 try, catch에서는 동일한 취급하게 되는 것입니다. 사용자가 직접 발생시킨 에러 이벤트를 처리하는 예는 다음과 같습니다. 

『chap07\UseThrowMain.java』
ⓙ───────────────────────────────────────
/**
사용자가 에러 이벤트를 직접 발생시키는 경우를 테스트하는 예
**/
public class UseThrowMain {
    public static void main(String args[]) {
        try { 
            throw new Exception("이것이 에러 메시지");
        } catch(Exception e) {
            System.out.println("--Exception 발생구문--");
            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 UseThrowMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac UseThrowMain.java
C:\javasrc\chap07>java UseThrowMain
--Exception 발생구문--
정보:e.getMessage(): 이것이 에러 메시지
정보:e.toString(): java.lang.Exception: 이것이 에러 메시지
java.lang.Exception: 이것이 에러 메시지
        at UseThrowMain.main(UseThrowMain.java:7)
finally: 결국이리로 오는군요
***/
───────────────────────────────────────ⓑ

이 예제에서는 throw 키워드를 사용해서 사용자가 직접 에러 이벤트를 발생시키고 있습니다. throw로 에러 이벤트를 발생시키면 해당 이벤트가 발생하고, catch에서 해당 이벤트를 처리하게 됩니다. 물론 finally는 무조건적으로 실행하겠죠. 그리고 BasicException.java에서 Exception 메시지가 null이었는데, Exception을 발생시킬 때 메시지를 넣어서 에러 이벤트를 발생시키고 있습니다. 만약 여러분이 에러 이벤트에 에러 메시지를 포함시키고 싶다면 다음과 같이 에러 이벤트를 생성할 때 메시지를 첨부시키면 됩니다.

▣ 메시지와 함께 수동으로 에러 이벤트를 발생시키는 예
◈ throw new Exception("이것이 에러 메시지");

사용자가 직접 Exception 이벤트를 생성시킬 때 위와 같이 이벤트 메시지를 포함시킬 수도 있습니다. 이 에러 이벤트의 메시지는 Exception e의 getMessage() 메서드를 이용해서 다시 받아낼 수 있습니다.

▣ throw 키워드
◈ 사용자가 직접 에러 이벤트를 발생시키고 싶을 때 사용하는 키워드


 




7.3.3 다단계 catch



에러 이벤트는 에러의 종류에 따라 상당히 많은 종류의 이벤트가 존재합니다. 하지만 모든 에러 이벤트는 Exception 클래스를 상속받아 만들어지기 때문에 에러 이벤트를 Exception류라고 생각하시면 됩니다.

▣ 이벤트들의 부모 클래스
◈ 모든 에러 이벤트는 Exception 클래스를 상속받는다.

상당히 많은 종류의 에러 이벤트가 있기 때문에 이것에 수반되는 문제 또한 존재합니다. 만약 try에서 발생하는 이벤트가 10 종류라면 10 종류 모두를 catch로 구현해야 하는 문제점이 있습니다. 이러한 문제를 해결하기 위해서 catch를 단계별로 사용하는 방법을 이용합니다.

▣ catch의 단계별 사용
◈ try에서 발생하는 모든 에러를 catch로 구현할 수 없기 때문에 catch를 단계별로 사용한다.

간단한 예를 알아보고 이것에 대해서 좀 더 논의해 보도록 하죠. 다음과 같이 여러 가지 catch를 사용하더라도 정작 필요한 NullPointerException을 처리해 주지 않았기 때문에 에러가 발생하고 있습니다.

『chap07\LevelErrorMain.java』
ⓙ───────────────────────────────────────
/**
catch 블록으로 처리하지 못한 경우
NullPointerException 블록이 없기 때문에 에러 발생
**/
import java.io.*;
public class LevelErrorMain{
    public static void main(String[] args){
        try{
            FileReader f = new FileReader("LevelErrorMain.java");
            String s = null;
            System.out.println(s.toString()); //NullPointerException 발생
        }catch(FileNotFoundException e1){
            System.out.println("FileNotFoundException:" + e1);
        }catch(ArrayIndexOutOfBoundsException e2){
            System.out.println("ArrayIndexOutOfBoundsException:" + e2);
        }
    } //end of main
} //end of LevelErrorMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac LevelErrorMain.java
C:\javasrc\chap07>java LevelErrorMain
Exception in thread "main" java.lang.NullPointerException
        at LevelErrorMain.main(LevelErrorMain.java:10)
***/
───────────────────────────────────────ⓑ

위의 예제서는 FileReader를 위해서 FileNotFoundException을, 그리고 필요하지 않지만 테스트를 위해서 ArrayIndexOutOfBoundsException을 catch 블록에서 처리하고 있습니다. 

▣ 예제에서 처리하는 에러 이벤트
◈ catch(FileNotFoundException e1){...}
◈ catch(ArrayIndexOutOfBoundsException e2){...}

그렇지만 실제 필요한 NullPointerException은 처리해 주지 않았기 때문에 에러가 발생합니다. 모든 에러를 프로그래머가 직접 catch로 명시해 줄 수는 없습니다. 이러한 문제는 다음과 같이 catch를 단계별로 처리해 주면 쉽게 해결할 수 있습니다.

▣ catch의 단계별 사용
◈ try{
◈         //작업
◈ } catch(FileNotFoundException e1) {
◈         //FileNotFoundException 에러처리
◈ } catch(ArrayIndexOutOfBoundsException e2) {
◈         //ArrayIndexOutOfBoundsException 에러처리
◈ } catch(Exception e3) {
◈         //그 외의 모든 에러
◈ }

제일 먼저 파일에 관련된 에러를 처리하고 그 다음 배열에 관련된 에러를 처리하고 있습니다. 그 외의 모든 에러는 Exception에서 처리하게 만드는 것입니다. 만약 try에서 NullPointerException이 발생했다면 catch 블록을 호출할 때 다음과 같은 가상의 구문으로 에러 이벤트가 할당되는 것을 생각해 볼 수 있습니다.

▣ 발생한 NullPointerException을 x라고 할 때 이벤트 x가 catch의 매개변수로 할당되는 예
◈ FileNotFoundException e1 = x; //불가능
◈ ArrayIndexOutOfBoundsException e2 = x; //불가능
◈ Exception e3 = x; //가능

위의 가상 코드에서 가능한 캐스팅의 구문은 NullPointerException x가 Exception으로 캐스팅되는 것입니다. 결과적으로 Exception은 업캐스팅의 원리에 의해서 모든 에러 이벤트를 처리할 수 있습니다. 모든 에러 이벤트가 Exception 클래스를 상속받기 때문에 NullPointerException이 Exception으로 업캐팅이 가능한 것입니다. 모든 에러를 처리할 수 있는 Exception을 추가한 예제는 다음과 같습니다.

『chap07\LevelCatchMain.java』
ⓙ───────────────────────────────────────
/**
Exception으로 모든 에러 처리
**/
import java.io.*;
public class LevelCatchMain{
    public static void main(String[] args){
        try{
            FileReader f = new FileReader("LevelCatchMain.java");
            String s = null;
            System.out.println(s.toString()); //NullPointerException 발생
        }catch(FileNotFoundException e1){
            System.out.println("FileNotFoundException:" + e1);
        }catch(ArrayIndexOutOfBoundsException e2){
            System.out.println("ArrayIndexOutOfBoundsException:" + e2);
        }catch(Exception e3){
            System.out.println("Exception:" + e3);
        }
    } //end of main
} //end of LevelCatchMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac LevelCatchMain.java
C:\javasrc\chap07>java LevelCatchMain
Exception:java.lang.NullPointerException
***/
───────────────────────────────────────ⓑ

위의 예제에서 NullPointerException 이벤트는 처리하지 않았지만 catch 구문에서 처리되고 있습니다. 이것은 Exception이 모든 에러를 처리할 수 있기 때문입니다. 만약 모든 에러를 처리하고자 하거나 발생할 에러가 무엇인지 모른다면 다음과 같이 에러처리를 한다면 모든 에러를 감지할 수 있을 것입니다.

▣ 모든 에러 이벤트에 대한 처리
◈ try{
◈         //...작업
◈ }catch(Exception e){ 
◈         //...모든 에러 이벤트 처리
◈ }

이와 같이 한다면 모든 에러 이벤트를 처리할 수 있습니다. 하지만 여러분들이 주의해야 되는 것이 있습니다. 위의 예제에서 다음과 같은 순서로 catch 블록을 사용하시면 안됩니다. 

▣ catch의 순서가 잘못된 경우(잘못된 catch의 단계별 사용)
◈ try{ 
◈        //....작업
◈ } catch(Exception e3){//모든 에러를 처리하기 때문
◈        //...에러처리 코드
◈ } catch(FileNotFoundException e1){
◈        //...에러처리 코드
◈ } catch(ArrayIndexOutOfBoundsException e2){
◈        //...에러처리 코드
◈ }

Exception이 제일 먼저 나오게 되면 catch(Exception e3)에서 모든 에러를 처리해 버리기 때문에 결코 다른 catch 블록으로는 갈 수 없습니다. 그렇기 때문에 이것은 컴파일할 때 다음과 같은 2개의 에러를 만나게 됩니다. 
 
▣ catch의 순서가 잘못되었을 때 발생하는 컴파일 에러
◈ exception java.io.FileNotFoundException has already been caught
◈     }catch(FileNotFoundException e1){
◈     ^
◈ exception java.lang.ArrayIndexOutOfBoundsException has already been caught
◈     }catch(ArrayIndexOutOfBoundsException e2){
◈     ^
◈ 2 errors

예제에서 catch 블록을 바꾸어서 테스트해 보시기 바랍니다. 사실 catch는 if문과 비슷한 성질을 가지고 있습니다. 다음의 if문에서 제일 마지막 else에 해당하는 부분이 바로 catch(Exception e3)와 같은 성질을 가지고 있습니다.

◈ if(조건){...}
◈ else if(조건){...}
◈ else if(조건){...}
◈ else if(조건){...}
◈ else{....} //그외의 경우

이것으로 catch의 다단계 사용을 마치고 의무적인 에러처리에 대해서 학습하도록 하겠습니다.



7.3.4 의무적인 에러처리



앞에서도 잠깐 언급했지만 에러가 발생할 가능성이 높은 곳에는 프로그래머가 의무적으로 에러 루틴을 넣어야 합니다. 즉 자바는 필요한 곳에 반드시 에러처리 루틴을 넣어야만 컴파일되는 방식입니다. 의무적으로 에러처리 루틴이 필요한 곳은 다음과 같습니다.

▣ 의무적으로 에러처리 루틴이 필요한 곳
◈ 네트웍 입출력
◈ 데이터베이스 입출력
◈ 파일 입출력
◈ 메모리 입출력

이 중에서 파일 입출력을 테스트해 보도록 하겠습니다. 다음은 파일을 열고 닫는 가장 간단한 방법입니다.

『chap07\NeedErrorMain.java』
ⓙ───────────────────────────────────────
/**
필수적으로 에러루틴이 필요한 경우(에러)
**/
import java.io.*;
public class NeedErrorMain {
    public static void main(String args[]) {
        //파일 열기
        FileReader f = new FileReader("NeedThrowExceptionMain.java"); 
        //...파일 입출력 작업
        f.close();//파일 닫기
    } //end of main
}//end of NeedErrorMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac NeedErrorMain.java
NeedErrorMain.java:8: unreported exception java.io.FileNotFoundException;
must be caught or declared to be thrown
        FileReader f = new FileReader("NeedThrowExceptionMain.java");
        ^
NeedErrorMain.java:10: unreported exception java.io.IOException; 
must be caught or declared to be thrown
        f.close();//파일 닫기
         ^
2 errors
***/
───────────────────────────────────────ⓑ

위의 예제는 의무적으로 에러 루틴을 사용해야 하는데, 사용하지 않았기 때문에 에러가 발생하는 예제입니다. 위의 예제에서 다음과 같은 에러를 처리해 주어야 합니다.

▣ java.io.FileNotFoundException 에러처리 루틴이 필요한 곳
◈ new FileReader("NeedThrowExceptionMain.java")

▣ java.io.IOException 에러처리 루틴이 필요한 곳
◈ f.close();

FileNotFoundException과 IOException 처리를 해 주어야만 컴파일을 할 수 있습니다. 컴파일 자체가 되지 않기 때문에 프로그래머는 무조건 에러처리 루틴을 넣어야 합니다. 다음은 에러 루틴을 삽입한 예입니다.

『chap07\NeedCatchMain.java』
ⓙ───────────────────────────────────────
/**
파일 입출력을 위해 에러처리 루틴을 삽입한 예
**/
import java.io.*;
public class NeedCatchMain {
    public static void main(String args[]) {
        System.out.println("프로그램 시작");
        try{
            FileReader f = new FileReader("NeedCatchMain.java");
            //...파일 입출력 작업
            f.close();//파일 닫기
        }catch(FileNotFoundException e1){
            e1.printStackTrace();
        }catch(IOException e2){
            e2.printStackTrace();
        }
        System.out.println("프로그램 종료");
    } //end of main
} //end of NeedCatchMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac NeedCatchMain.java
C:\javasrc\chap07>java NeedCatchMain
프로그램 시작
프로그램 종료
***/
───────────────────────────────────────ⓑ

만약 2개의 에러처리를 하지 않고 모든 에러 이벤트를 처리하는 Exception을 사용하고자 한다면 다음과 같이 하면 됩니다.

▣ 모든 에러 에벤트의 처리
◈ try{
◈        //작업
◈ } catch(Exception e){
◈        //에러처리
◈ }

▣ Exception 클래스의 사용은?    
◈ 에러 이벤트를 정확하게 모를 경우에 에러 이벤트의 최상위 클래스인 Exception형을 사용하면 된다. 
◈ Exception을 사용할 경우 어떠한 에러 이벤트가 발생하더라도 Exception형으로 자동 업캐스팅되기 때문에 모든 에러를 catch 블록에서 처리할 수 있다.

파일 입출력에 관련된 에러 루틴의 의무적인 사용 방법에 대해서 알아보았습니다. 네트웍에 관련된 것도 테스트해 보도록 하죠.

『chap07\NeedNetErrorMain.java』
ⓙ───────────────────────────────────────
/**
네트웍에 관련된 의무적 에러처리 루틴을 테스트하는 예(에러발생)
**/
import java.net.*;
public class NeedNetErrorMain{
    public static void main(String args[]) {
        URL url = new URL("http://www.yahoo.co.kr");
        System.out.println("URL:" + url.toString());
    } //end of main
} //end of NeedNetErrorMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac NeedNetErrorMain.java
NeedNetErrorMain.java:7: unreported exception java.net.MalformedURLException;
must be caught or declared to be thrown
        URL url = new URL("http://www.yahoo.co.kr");
                  ^
1 error
***/
───────────────────────────────────────ⓑ

이 예제에서 java.net.URL 객체를 생성하고 있습니다. URL 객체를 생성할 때는 반드시 에러처리를 해 주어야만 컴파일이 됩니다. 컴파일 에러에서 제시하고 있는 것처럼 MalformedURLException 이벤트에 대한 에러처리를 한다면, 이 예제는 컴파일을 할 수 있을 것입니다. 아래는 수정한 프로그램입니다.

『chap07\NeedNetCatchMain.java』
ⓙ───────────────────────────────────────
/**
URL 객체 생성에 필요한 에러 루틴을 추가한 예
**/
import java.net.*;
public class NeedNetCatchMain {
    public static void main(String args[]) {
        System.out.println("프로그램 시작");
        try{
            URL url = new URL("http://www.yahoo.co.kr");
            System.out.println("URL:" + url.toString());
        } catch(MalformedURLException e) {
            e.printStackTrace();
        } finally{
            System.out.println("finally: 결국이리로 오는군요");
        }
        System.out.println("프로그램 종료");
    } //end of main
} //end of NeedNetCatchMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac NeedNetCatchMain.java
C:\javasrc\chap07>java NeedNetCatchMain
프로그램 시작
URL:http://www.yahoo.co.kr
finally: 결국이리로 오는군요
프로그램 종료
***/
───────────────────────────────────────ⓑ

별다른 결과는 얻을 수 없지만 URL 객체를 만들 때는 반드시 에러처리 루틴을 사용해야만 합니다.

'에러처리가 별 것 아니군'이라고 생각하시면 큰일납니다. 여러분이 계획하고 있는 프로그램이 상당히 크고 복잡하다면 실행 타임의 에러를 디버깅한다는 것은, 그리고 실행 타임에 발생하는 에러에 적절한 대비한다는 것은 대단히 어렵습니다. 만약 여러분이 데몬(Demon) 계열의 프로그램을 계획하고 계시다면, 에러가 발생하더라도 프로그램은 계속적으로 수행되어야 합니다. 이 때 에러처리를 제대로 하지 않는다면 여러분의 프로그램은 1시간도 버티기 힘들지도 모릅니다. 필요할 때 프로그램을 잘 종료시키는 것도 중요하지만, 원하기 전에는 죽지않는 프로그램을 만드는 것도 쉬운 일은 아닙니다. 다음으로 에러처리 미루기에 대해서 알아보도록 하겠습니다.



7.3.5 에러처리 미루기



특정 메서드를 사용할 때 에러처리를 하라는 메시지가 출력되면 무조건 에러처리 구문을 달아주면 됩니다. 하지만 이런 질문을 던질 수 있습니다. 내부에서 어떻게 했길래 메서드를 사용하기만 하면 나보고 에러처리를 하래? 그 답은 throws에 있습니다. 

▣ 질문
◈ 내부에서 어떻게 했길래 메서드를 사용하기만 하면 에러처리 루틴을 붙이라는 것일까요?

만약 여러분이 만든 메서드 내부에서 에러처리를 해야 하는데 에러처리를 하지 않고 메서드 끝에 다음과 같이 명시한다면, 메서드를 사용(호출)하는 사람에게 에러처리를 미루는 효과가 있습니다.

▣ MalformedURLException 에러처리를 미루는 예
◈ public void makeURL() throws MalformedURLException{...}

이 구문은 makeURL()의 내부에서 MalformedURLException 처리를 하지 않으니, 이 메서드를 사용하는 측에서 에러를 처리하라는 뜻입니다. 즉 메서드 내부에서 처리하지 않고 메서드를 사용할 때 에러처리를 하도록 에러처리를 미루는 것입니다.

이런 경우는 아주 쉽게 찾아 볼 수 있습니다. 보통의 경우 스트림에서 데이터를 기록하거나 읽어들일 때 try와 catch를 붙여주는 이유는 내부에서 에러처리를 미루고 있기 때문입니다. 내부에서 처리하지 않고, 코드를 작성하고 있는 프로그래머에게 에러치러를 전담시키는 효과가 있습니다. 대부분의 에러처리가 다 그렇지 않습니까?  URL 객체 생성의 예제에서 우리는 URL 객체를 생성하면서 에러처리를 해주었습니다. 이것은 URL 클래스의 생성자에서 다음과 같이 에러처리를 미루고 있기 때문입니다. 즉 throws 키워드를 URL 클래스의 생성자에서 사용했기 때문입니다.

▣ 생성자에서 MalformedURLException 에러처리를 미루고 있는 URL 클래스
◈ public class URL {
◈        public URL(String spec) throws MalformedURLException{
◈             //...생략
◈        }
◈        //...생략
◈ }

▣ throws
◈ throws를 이용해서 의무적 에러를 만들 수 있다.

내부의 메서드 내에서 에러처리 작업을 미루고 있기 때문에, 에러처리 미루기가 된 메서드를 사용하는 사람은 이 메서드를 호출할 때 try, catch 블록 처리를 해주어야 합니다. 이러한 에러처리의 예를 직접 만들어 보도록 하죠.

『chap07\ShiftError.java』
ⓙ───────────────────────────────────────
/**
에러 처리 미루기의 예(에러 발생)-makeURL()을 사용할 때 에러처리 필요
**/
import java.net.*;
public class ShiftError {
    //throws를 이용해서 에러 처리를 미무는 메서드
    public URL makeURL(String urlstr) throws MalformedURLException{
        return new URL(urlstr);
    }
    public static void main(String args[]) {
        ShiftError s = new ShiftError();
        //에러처리 루틴 필요
        URL url = s.makeURL("http://www.yahoo.co.kr");
    } //end of main
} //end of ShiftError class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac ShiftError.java
ShiftError.java:13: unreported exception java.net.MalformedURLException;
must be caught or declared to be thrown
        URL url = s.makeURL("http://www.yahoo.co.kr");
                   ^
1 error
***/
───────────────────────────────────────ⓑ

분명 위의 makeURL()에서 URL 객체를 생성할 때 에러처리를 해주어야 합니다. 메서드 내에 에러처리를 해주지 않고 메서드를 사용하는 곳에서 에러처리를 미루는 방법으로 메서드의 선언 부분에 throws MalformedURLException를 이용하고 있습니다. 이렇게 되면 makeURL()을 호출하는 곳에서 에러처리를 해 주어야 합니다. 다음의 구문에서는 컴파일 에러가 발생하지 않습니다.

▣ MalformedURLException 에러처리를 미루는 예
◈ public URL makeURL(String urlstr) throws MalformedURLException{ //2. 에러처리 미루기로 처리
◈         return new URL(urlstr); //1. 에러처리 루틴 필요
◈ }

makeURL() 메서드 내의 new URL(urlstr)을 사용할 때 try, catch를 붙여주어야 하지만, throws MalformedURLException을 이용해서 에러처리를 미루고 있습니다. 문제는 makeURL()을 호출하는 부분에 있습니다. 다음과 같이 메서드를 호출할 때 그냥 사용하면 컴파일 에러가 발생합니다.

▣ throws를 이용해서 에러처리를 미루고 있는 메서드의 호출(에러)
◈ URL url = s.makeURL("http://www.yahoo.co.kr");

makeURL() 메서드에서 에러처리를 미루었기 때문에 이 구문은 다음과 같이 try, catch를 달아준 후 사용해야 합니다.

▣ throws를 이용해서 에러처리를 미루고 있는 메서드의 호출(에러없음)
◈ try{
◈         URL url = s.makeURL("http://www.yahoo.co.kr");
◈ }catch(MalformedURLException e){
◈        //에러처리        
◈ }

아주 간단한 기법이지만 대부분의 라이브러리에서 이와 같은 방법을 사용합니다. 사용자가 라이브러리를 이용해서 객체를 만들고 객체에서 사용할 수 있는 메서드를 호출할 때, 사용자가 알아서 처리하도록 하는 것입니다. 다음은 위의 코드를 수정해서 컴파일 에러를 제거한 예입니다.

『chap07\ShiftCatch.java』
ⓙ───────────────────────────────────────
/**
에러 처리 미루기의 예(에러처리 미루기를 직접 구현하는 예)
**/
import java.net.*;
public class ShiftCatch {
    public URL makeURL(String urlstr) throws MalformedURLException{
        return new URL(urlstr);
    }
    public static void main(String args[]) {
        ShiftCatch p = new ShiftCatch();
        try{    
            //정확한 URL을 입력하지 않았기 때문에 에러발생
            URL url = p.makeURL("htttttp://www.yahoo.co.kr");
        } catch(MalformedURLException e) {
            e.printStackTrace();
        } finally{
            System.out.println("finally: 결국이리로 오는군요");
        }
    } //end of main
} //end of ShiftCatch class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac ShiftCatch.java
C:\javasrc\chap07>java ShiftCatch
java.net.MalformedURLException: unknown protocol: htttttp
        at java.net.URL.<init>(URL.java:586)
        at java.net.URL.<init>(URL.java:476)
        at java.net.URL.<init>(URL.java:425)
        at ShiftCatch.makeURL(ShiftCatch.java:7)
        at ShiftCatch.main(ShiftCatch.java:13)
finally: 결국이리로 오는군요
***/
───────────────────────────────────────ⓑ

URL 클래스의 생성자가 에러처리를 하지 않고 미룬 것을 makeURL() 내부에서 처리하지 않고 다시 미루고 있습니다. 위의 예제는 makeURL() 메서드를 사용할 때 에러처리를 했기 때문에 에러가 발생하더라도 프로그램은 무사히 종료하게 됩니다.

▣ 에러처리 미루기(throws 키워드)
◈ 에러 이벤트를 메서드 내부에서 처리하지 않고 메서드를 사용할 때 에러처리를 하도록 만드는 기법
◈ throws는 에러처리를 미룰 때 사용하는 키워드이다. 
◈ 앞에서 나온 throw 키워드와 혼돈하지 말자.



7.3.6 try, catch, finally의 미묘한 사용



finally 블록은 에러 루틴에서 반드시 수행되는 부분입니다. 에러가 나든 나지 않든 무조건 실행되는 부분입니다. 이번에는 finally의 실제적인 사용법에 대해서 알아보도록 하겠습니다. 간단한 예를 하나 들어보죠. 폭발물을 다루는 컴퓨터가 있다고 가정하겠습니다. 이 컴퓨터를 다루는 사용자는 다음과 같은 순으로 작업을 할 것입니다.

▣ 폭발물 컴퓨터의 작업내용
◈ 폭발물 컴퓨터의 전원을 켠다. - powerOn()
◈ 폭발물 컴퓨터로 작업을 한다. - process()
◈ 폭발물 컴퓨터의 전원을 끈다. - powerOff()

이러한 작업을 하던 도중에 에러가 발생한다면 어떻게 에러처리를 할 것인가에 대한 방법론을 프로그램적으로 작성해 보겠습니다. 다음은 폭발물 컴퓨터에 대한 코드입니다.

『chap07\BombComputer.java』
ⓙ───────────────────────────────────────
/**
폭발물 처리 컴퓨터
**/
public class BombComputer {
    boolean power = false;
    public void powerOn() { 
        power = true; 
        System.out.println("폭발물 처리 컴퓨터 전원 ON!");
    }
    public void powerOff() { 
        power = false; 
        System.out.println("폭발물 처리 컴퓨터 전원 OFF!");
    }
    public void process() throws Exception{
        System.out.println("작업처리 1");
        System.out.println("작업처리 2");
        //무조건 에러가 발생하도록 디자인
        throw new Exception("작업처리 3 오류발생 곧 폭발합니다.!@#$");
    }
} //end of BombComputer class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac BombComputer.java
***/
───────────────────────────────────────ⓑ

BombComputer는 전원을 켜고 그리고 작업을 진행하면 작업하는 도중 폭발하게 되는 역할을 담당하고 있습니다. BombComputer를 동작시키는 프로그램을 작성해 보도록 하죠. 다음은 BombComputer를 일반적인 try, catch만을 이용해서 동작시키는 예입니다.

『chap07\BombComputerErrorMain.java』
ⓙ───────────────────────────────────────
/**
BombComputer를 동작시키는 프로그램(try, catch만 사용한 경우)
**/
public class BombComputerErrorMain {
    public static void main(String args[]) {
        BombComputer bc = new BombComputer();
        try{
            bc.powerOn();
            bc.process();
            bc.powerOff();
        }catch(Exception e) {
            e.printStackTrace();
        } 
    }//end of main
} //end of BombComputerErrorMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac BombComputerErrorMain.java
C:\javasrc\chap07>java BombComputerErrorMain
폭발물 처리 컴퓨터 전원 ON!
작업처리 1
작업처리 2
java.lang.Exception: 작업처리 3 오류발생 곧 폭발합니다.!@#$
        at BombComputer.process(BombComputer.java:18)
        at BombComputerErrorMain.main(BombComputerErrorMain.java:6)
***/
───────────────────────────────────────ⓑ

BombComputerErrorMain의 경우는 전원을 On한 후 작업을 하고, 그리고 나서 전원을 Off하는 일반적인 과정입니다. 여러 가지 작업들을 모아 한번에 에러처리를 하고 있습니다. 하지만 process()를 수행하는 도중 에러가 발생했을 때 컴퓨터의 전원은 꺼지지 않고 바로 폭발하게 됩니다. 자세히 관찰해 보시기 바랍니다. 모든 작업을 try 내에서 하고 있으며 catch에서는 에러만을 잡는 형식입니다. 다음의 구문이 실행되었을 때 BombComputer의 전원은 Off되지 않고 바로 폭발로 이어질 것입니다.

▣ 메서드 호출 순서
◈ bc.powerOn();
◈ bc.process(); //여기서 폭발(꽝!)
◈ bc.powerOff();

process()가 호출된 시점에서 바로 catch 블록으로 이동하기 때문입니다. 그렇다면 catch에서 전원을 내려주면 되지 않을까요? 물론 catch 블록에서 전원을 Off해도 상관은 없습니다. 하지만 이렇게 되면 다음과 같은 두 가지 문제가 발생합니다. 

▣ catch에 powerOff()를 사용할 때의 문제점
◈ 에러가 나지 않을 것을 대비해서 try내에도 powerOff()를 해주어야 하며, 에러가 날 것을 대비해서 catch에도 powerOff()를 해주어야 합니다.
◈ 에러가 한두개가 아니라 20개정도 처리해야 한다고 가정하죠. 이렇게 되면 모든 catch에 powerOff()를 사용해야 하는 단점이 있다.

위와 같은 문제점은 finally를 이용하면 간단히 해결할 수 있습니다. finally를 추가한 예를 살펴보도록 하겠습니다.
 
『chap07\BombComputerMain.java』
ⓙ───────────────────────────────────────
/**
finally를 추가한 예
**/
public class BombComputerMain {
    public static void main(String args[]) {
        BombComputer bc = new BombComputer();
        try{
            bc.powerOn();
            bc.process();
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            bc.powerOff();
        }
    } //end of main
} //end of BombComputerMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac BombComputerMain.java
C:\javasrc\chap07>java BombComputerMain
폭발물 처리 컴퓨터 전원 ON!
작업처리 1
작업처리 2
java.lang.Exception: 작업처리 3 오류발생 곧 폭발합니다.!@#$
        at BombComputer.process(BombComputer.java:18)
        at BombComputerMain.main(BombComputerMain.java:6)
폭발물 처리 컴퓨터 전원 OFF!
***/
───────────────────────────────────────ⓑ

수정된 BombComputerMain 예제에서는 에러가 나든 나지 않든 작업을 끝내면 무조건적으로 전원을 Off시키고 있습니다. 물론 가상으로 이 두 경우를 테스트해 보았지만 실제 우리가 프로그램 내에서 범하는 오류는 아주 많습니다. 되도록이면 BombComputerMain에서 사용하는 것과 같은 안정된 코드를 작성한다면 프로그램의 맛은 달라질 것입니다.

▣ finally 블록
◈ 에러처리 루틴에서 에러 발생 여부에 관계 없이 무조건 실행되는 블록



저작자 표시 비영리 변경 금지

'JAVA PROGRAMMING > JAVA' 카테고리의 다른 글

8.2 스레드의 기본  (0) 2010/02/21
8.1 Thread and Synchronization  (0) 2010/02/21
7.3 에러처리 실례  (0) 2010/02/21
7.2 Exception  (0) 2010/02/21
7.1 Exception  (0) 2010/02/21
6.4 컬렉션과 맵  (0) 2010/02/21
1  ... 46 47 48 49 50 51 52 53 54  ... 156 
BLOG main image
(주)KinTeL 회장 김형기

카테고리

분류 전체보기 (156)
My Name Is KinTeL (3)
농구 인생 (1)
사진 이야기 (12)
미디어 (5)
JAVA PROGRAMMING (105)
C# Programming (2)
FLEX (6)
Database (4)
About Eclipse (1)
참조 - Reference (9)
모든 무료 정보 (2)
좋은글 (2)


Statistics Graph
Total : 72,316
Today : 25
Yesterday : 41

최근에 받은 트랙백