7.2 Exception

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



7.2.1 에러처리란?



보통 에러는 컴파일 타임의 에러와 실행 타임의 에러로 구분되어지고 있습니다. 컴파일 타임의 에러는 문법적인 에러이며, 이것은 자바 구문상의 에러입니다. 모든 에러가 컴파일 타임에 발견될 수 있는 것은 아닙니다. 사실 컴파일 타임의 에러야 어떤 식으로든 찾으면 됩니다. 대부분 기본적인 문법을 지키지 않아서 발생하는 에러이기 때문에 어렵지 않게 찾을 수 있습니다. 하지만 컴파일이 되었다 하더라도 실행하는 동안에 에러가 발생할 수도 있습니다. 실행 타임의 에러는 프로그램 로직(Logic)상의 오류이기 때문에 그렇게 만만한 것은 아닙니다.

▣ 컴파일 타임 오류(Compile-Time Error)
◈ 자바의 문법적 오류로 컴파일이 되지 않는 구문상의 오류

▣ 실행 타임 오류(Run-Time Error)
◈ 컴파일은 되지만 실행이 되지 않는 로직(Logic)상의 오류

이 장에서 학습하는 내용은 문법적인 오류를 위한 것이 아니라 프로그램 로직(Logic)상의 오류를 말합니다. 먼저 Exception의 의미부터 알아보도록 하죠. Exception이란 무엇일까요? Exception은 실행 타임에 발생하는 에러 이벤트(Error Event)를 의미합니다.

▣ Exception의 정의
◈ Exception은 실행 타임에 발생하는 에러 이벤트(Error Event)를 말한다.

자바에서는 에러가 발생했을 때 에러 이벤트(Error Event)라는 것이 발생합니다. 이 이벤트를 Exception이라고 합니다. 프로그램에서 에러가 발생할 때 Exception 이벤트가 발생하고, 발생된 이벤트는 프로그래머가 처리하지 않으면 곧장 가상머신으로 넘어가게 됩니다. 가상머신에서는 이 이벤트를 받았을 때 에러 이벤트의 성격에 따라서 적절한 조치를 취하게 됩니다. 대부분의 경우 프로그램을 중지시키는 루틴을 실행하게 됩니다.

그렇다면 Exception 이벤트가 가상머신에 의해서 자동으로 처리되기 전에 에러를 처리하려 할 것입니다. 실행 타임에 에러 이벤트가 발생했을 때 프로그래머가 이를 먼저 감지하기 위한 도구가 바로 try와 catch입니다.

쉬운 예를 들어보죠. 파일을 로딩하려는데 파일이 없다면 File Not Found Exception이 발생할 것입니다. 이 에러가 발생하기 전에 파일이 있는지 없는 지를 검사하는 루틴을 넣어서 프로그램한다면 절대 에러는 발생하지 않을 것입니다. 보통의 File 라이브러리들은 파일의 존재 여부를 확인하는 메서드 정도는 제공하고 있습니다. 이를 이용한다면 File Not Found Exception이 발생하기 전에 해결할 수 있습니다.

▣ 에러처리 방법 I
◈ 프로그램적으로 에러가 발생하지 않도록 완벽하게 코딩

프로그램 실행 시에 파일이 존재하지 않는다면 그리고 파일 존재 여부를 확인하는 루틴을 넣지 않았다면 File Not Found Exception은 곧장 가상머신으로 보내질 것입니다. 이 중간에 프로그래머가 File Not Found Exception을 가로채기 위해서 우선 try로 에러 감지 부분을 설정하고, 이 부분에서 에러가 감지되면 처리 루틴인 catch로 보낼 것입니다.

▣ try와 catch 블록의 사용
◈ try{
◈         //파일로딩
◈         //try 블록은 에러 감지블록
◈ }catch(FileNotFountException e){
◈         //catch 블록은 에러 처리블록
◈ }

▣ 에러처리 방법 II
◈ try와 catch를 이용해서 프로그래머가 에러를 감지해서 처리

에러가 발생하지 않도록 완벽하게 코딩하는 것과 try, catch 루틴을 사용하는 것은 아주 미세한 차이가 있습니다. 예전의 방식은 try, catch 보다는 프로그래머가 직접 처리하는 것을 더 선호했습니다. 하지만 자바에서는 try와 catch를 필요한 곳에 의무적으로 사용하도록 하고 있습니다. 

▣ 참고
◈ 위의 경우에 설명을 위해서 사용자가 파일의 유무를 확인하는 에러처리 기법을 소개하고 있지만, 자바에서는 파일을 다룰 때 try, catch를 의무적으로 사용해야 합니다.

▣ 에러처리의 의무화
◈ 자바에서는 에러가 발생할 가능성이 높은 곳에 try와 catch의 사용이 의무적이다.

표준 C++에서는 try, catch를 사용하든 하지 않든 그것은 전적으로 프로그래머의 책임이었습니다. 프로그래머가 필요하다고 생각될 때 try, catch를 이용하면 됐습니다. 하지만 자바에서는 실행 타임에 에러가 발생할 가능성이 있는 곳이면 컴파일러 차원에서 의무적으로 에러처리 루틴을 붙여주도록 하고 있습니다. 즉 에러가 발생할 가능성이 높은 곳은 무조건 try와 catch를 붙여야만 합니다. 에러처리 루틴을 컴파일러 차원에서 사용하도록 강요하는 것입니다. C나 C++에서는 컴파일러 차원에서 체크하지 않았는데, 자바에서는 에러 발생 확률이 높은 곳은 반강제적으로 넣도록 하고 있습니다. 다음과 같이 코딩하면 컴파일할 때 에러가 발생합니다.

『chap07\ErrorMain.java』
ⓙ───────────────────────────────────────
/**
FileReader는 의무적으로 FileNotFoundException을
처리해 주어야 하지만 예외처리를 하지 않은 경우
**/
import java.io.*;
public class ErrorMain{
    public static void main(String[] args) {
        FileReader fr = new FileReader("ErrorMain.java");
    } //end of main
} //end of ErrorMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac ErrorMain.java
ErrorMain.java:4: unreported exception java.io.FileNotFoundException; 
must be caught or declared to be thrown
                FileReader fr = new FileReader("ErrorMain.java");
                                ^
1 error
***/
───────────────────────────────────────ⓑ

위의 에러는 FileReader라는 것을 사용하면서도 에러처리 루틴을 삽입하지 않았기 때문에 발생하는 에러입니다. 컴파일러 차원에서 에러처리 루틴이 있는지 없는지 검사하기 때문에 FileReader라는 것을 사용할 때는 반드시 try와 catch를 이용해서 FileNotFoundException 처리를 해주어야 합니다. 위의 코드는 다음과 같이 수정한다면 컴파일을 무사히 통과할 것입니다.

『chap07\ErrorMain2.java』
ⓙ───────────────────────────────────────
/**
try, catch를 이용해서 FileNotFoundException을 처리한 경우
**/
import java.io.*;
public class ErrorMain2{
    public static void main(String[] args){
        try{
            FileReader fr = new FileReader("ErrorMain.java");
            //fr을 이용해서 작업
        }catch(FileNotFoundException e){
            System.out.println(e);
        }
    } //end of main
} //end of ErrorMain2 class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac ErrorMain2.java
C:\javasrc\chap07>java ErrorMain2
***/
───────────────────────────────────────ⓑ

어디에 try-catch를 붙여야 할 지는 컴파일 에러를 통해서 컴파일러가 친절히 알려 줄 것입니다. 대부분의 경우 입출력에 관계된 곳이면 에러처리 루틴을 무조건 붙인다고 생각하시면 됩니다. 의무적으로 에러 루틴을 사용해야 하는 입출력 작업은 다음과 같습니다.

▣ 입출력 작업을 할 때 에러처리를 붙여야 하는 곳
◈ 네트웍 입출력
◈ 데이터베이스 입출력
◈ 파일 입출력
◈ 메모리 입출력

이 이외에도 입출력과 관련된 곳이면 에러 루틴이 필수적입니다. 사실 위의 목록을 제외한다면 프로그램이 별 의미가 없죠. 

▣ 에러처리 루틴의 의무화
◈ 에러가 자주 발생하는 곳에는 컴파일러 차원에서 에러처리 루틴을 요구하기 때문에 의무적으로 에러처리 루틴을 넣어야 한다.

프로그램 동작시 발생하는 에러가 어디서 발생했는지 찾기란 상당히 어렵습니다. 실행 타임의 에러를 미연에 방지하는 차원에서 에러처리 루틴을 의무화한 것입니다. 이렇게 함으로써 실행 타임의 에러는 줄이고 더욱 튼튼한 프로그램 작성을 유도하고 있는 것입니다. 

▣ 에러처리 구문을 사용하는 이유
◈ 실행 타임의 에러를 미연에 방지하기 위해서
◈ 에러 이벤트가 발생했을 때 발생한 위치의 확인을 위해서
◈ 에러 이벤트가 발생했을 때 적절하게 대처하기 위해서

에러가 발생했을 때 발생한 이벤트를 처리해 줄 수 있도록 하는 것이 바로 에러처리 구문입니다. 에러처리의 사용법은 간단하지만 잘못 사용하면 의미가 없어져 버릴 수도 있습니다. 적절하게 잘 사용하면 아주 매력적인 프로그램을 작성할 수 있습니다.



7.2.2 기본적인 에러처리



에러처리는 try-catch-finally 순으로 사용하게 됩니다. 이것을 사용했을 때는 프로그램의 소스 코드가 상당히 길어지게 됩니다. 하지만 에러처리의 강력함을 알고 있다면, 그 정도는 너그럽게 봐주는 것이 옳을 것입니다. 일단 try-catch-finally 구문부터 한번 살펴보도록 하겠습니다.

▣ try, catch, finally를 이용한 에러 처리기(Exception Handler)
◈ try {
◈         // 에러를 발생할 가능성이 있는 코드
◈ } catch(에러이벤트1 변수) {    
◈        // 에러이벤트1이 발생했을 때 이벤트가 catch로 넘어오게 된다.
◈         //에러 처리
◈ } catch(에러이벤트2 변수) {    
◈        // 에러이벤트2가 발생했을 때 이벤트가 catch로 넘어오게 된다.
◈         //에러처리
◈ } finally{
◈         // 최종적으로 무조건적으로 처리해야 하는 작업
◈ }

try, catch는 두 가지 관점에서 생각해 볼 수 있습니다. 첫째는 에러처리를 반드시 해주어야 하기 때문에 사용하는 경우입니다. 일단 컴파일이 되지 않기 때문에 이것은 어쩔 수 없는 것입니다. 둘째는 에러 루틴이 필요 없지만 특정 부분에 에러가 발생할 가능성이 높다고 판단될 때 프로그래머가 임의로 사용할 수도 있습니다.

▣ 에러처리를 하는 경우
◈ 컴파일 차원에서 점검하는 에러(의무적)
◈ 에러처리 루틴이 의무적으로 필요한 것은 아니지만 사용자가 필요하다고 생각될 때 사용하는 경우(선택적)

에러처리를 하고자 하는 영역을 try 블록으로 설정합니다. try는 말 그대로 이 구문에서 에러가 발생하는지 시도해 보겠다는 의미가 됩니다. try 블록에서 에러 이벤트가 발생하면, 에러 이벤트를 try가 감지해서 적절한 catch 블록으로 보낸 후 처리하게 합니다. 에러가 있다면 이벤트가 발생하게 되고, try에서 발생한 에러 이벤트를 catch로 잡는 방식입니다. catch 블록에는 처리할 에러 이벤트를 명시해 주어야 하며, 블록 내에는 발생한 에러에 대한 처리를 위한 코딩을 하면 됩니다.

에러 이벤트의 종류는 아주 많습니다. 그렇기 때문에 try 하나에 여러 개의 catch가 존재할 수 있습니다. 이 말은 에러 이벤트에 따라서 처리하는 각각의 catch가 필요하다는 뜻입니다. 에러처리 순서는 다음과 같습니다.

다중 catch를 이용한 에러 처리【chap07\exceptionhandler.bmp】
 

   

try 블록 내부에서 에러가 발생하지 않는다면 try, catch는 아무일도 하지 않습니다. 위의 그림에서는 고의로 NullPointerException 에러가 발생하는 코드를 삽입했기 때문에 NullPointerException을 처리하는 catch가 동작하게 되는 것입니다.

▣ 고의로 에러를 발생한 경우(NullPointerException)
◈ String s = null
◈ System.out.println(s.length());

메모리가 없는 객체에 점을 찍는다면 참조할 메모리가 없다는 NullPointerException이 발생할 것입니다. s.length()라는 문장이 실행되면 자동으로 에러 이벤트가 발생하게 되고, catch 블록에서 NullPointerException 이벤트를 검색하게 됩니다. 만약 처리할 수 있는 catch 블록이 있다면, 해당 catch 블록이 호출됩니다. catch 블록이 호출될 때 try에서 발생한 이벤트는 catch의 매개변수로 들어가게 됩니다.

발생한 NullPointerException을 x라고 가정한다면, catch에서 다음과 같이 발생한 이벤트가 매개변수로 전달됩니다.

▣ 에러 이벤트 x가 catch의 매개변수로 전달되는 과정
◈ NullPointerException e1 = x;

catch가 처리되고 나면 finally는 무조건적으로 실행하게 됩니다. try에서 에러가 발생하든 하지 않든 프로그램에서 최후의 마무리 작업은 해주어야 합니다. 그 뒷마무리 작업을 finally 블록이 담당하고 있습니다. 에러가 발생하더라도, try나 catch의 중간에 return이 되더라도, finally 블록은 반드시 실행을 하게 됩니다. 일단 에러가 발생하는 예제를 하나 만들어 보죠. 다음은 에러처리 루틴이 없는 상황에서 에러가 발생하는 예입니다.

『chap07\NullErrorMain.java』
ⓙ───────────────────────────────────────
/**
실행 시에 NullPointerException 에러가 발생하는 예
**/
public class NullErrorMain {
    public static void main(String[] args) {
        String str = null;
        System.out.println(str.length()); //에러발생
        System.out.println("프로그램의 종료");
    } //end of main
} //end of NullErrorMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac NullErrorMain.java
C:\javasrc\chap07>java NullErrorMain
Exception in thread "main" java.lang.NullPointerException
        at NullErrorMain.main(NullErrorMain.java:7)
***/
───────────────────────────────────────ⓑ

위의 예에서 str은 null입니다. 메모리가 생성되지 않은 str에서 멤버 메서드 length()를 호출하는 것은 잘못된 것이죠. 이럴 때 프로그램은 NullPointerException 이벤트를 발생시킵니다. 그런데 주의 깊게 볼 것이 있습니다. 에러가 발생했을 때 프로그램이 바로 종료한다는 것입니다. 프로그램의 마지막 부분에 있는 System.out.println("프로그램종료")까지 가보지도 못하고 프로그램이 끝나버린 것입니다. 자! 이제 try, catch를 붙여보도록 하겠습니다.

『chap07\TryCatchMain.java』
ⓙ───────────────────────────────────────
/**
에러처리 루틴이 삽입된 예(실행 시에 에러처리 루틴에 의해 에러가 처리된다.)
**/
public class TryCatchMain{
    public static void main(String[] args) {
        try{
            String str = null;
            System.out.println(str.length());
        }catch(NullPointerException e){
            System.out.println(e.toString() + " 에러가 발생했습니다");
            System.out.println("에러처리 루틴 실행");
        }
        System.out.println("프로그램의 종료");
    } //end of main
} //end of TryCatchMain cass
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac TryCatchMain.java
C:\javasrc\chap07>java TryCatchMain
java.lang.NullPointerException 에러가 발생했습니다
에러처리 루틴 실행
프로그램의 종료
***/
───────────────────────────────────────ⓑ

위와 같이 에러가 발생할 가능성이 높은 부분에 에러처리를 해주면, 에러 이벤트가 발생할 때 처리할 수 있는 catch 블록으로 에러가 처리되는 것입니다. 이렇게 에러처리를 해주면 프로그램이 멈추지 않고 끝까지 수행하게 됩니다. 이것은 에러처리를 했으니 프로그램이 안전하다는 의미입니다. 현재 catch는 NullPointerException 이벤트만을 처리할 수 있습니다. 만약 NullPointerException이 아닌 다른 이벤트가 명시되어 있다면, try 구문에서 발생하는 에러는 catch로 처리할 수 없습니다.

try 구문에서 발생하는 에러 이벤트를 catch로 잡아서 처리한다는 것을 꼭 기억하시기 바랍니다. 한번 더 강조하지만 catch는 try에서 발생하는 에러 이벤트만을 잡을 수 있으며,  발생한 에러 이벤트와 catch의 처리 이벤트가 다르다면 발생한 에러 이벤트를 catch에서 처리할 수 없습니다.

▣ 에러 이벤트와 catch의 매개변수
◈ try에서 발생한 에러 이벤트와 catch의 매개변수가 일치해야만 catch에서 처리할 수 있다.

지금까지 에러를 처리하는 기본적인 방법에 대해서 배워 보았습니다. 물론 try와 catch만으로 처리하는 것을 알아보았고, 아직 finally는 보지도 못했습니다. 이제 마지막으로 finally까지 포함한 예제를 만들어 보도록 하죠.

『chap07\TryCatchFinallyMain.java』
ⓙ───────────────────────────────────────
/**
에러처리 루틴(try, catch, finally)이 삽입된 예
finally 블록의 무조건적인 실행
**/
public class TryCatchFinallyMain{
    public static void main(String[] args) {
        try{
            String str = null;
            System.out.println(str.length());
        }catch(NullPointerException e){
            System.out.println(e.toString() + " 에러가 발생했습니다");
            System.out.println("에러처리 루틴 실행");
        }finally{
            //에러가 나든 나지 않든 무조건 실행되는 블록    
            System.out.println("finally 구문 실행");
        }
        System.out.println("프로그램의 종료");
    } //end of main
} //end of TryCatchFinallyMain cass
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap07>javac TryCatchFinallyMain.java
C:\javasrc\chap07>java TryCatchFinallyMain
java.lang.NullPointerException 에러가 발생했습니다
에러처리 루틴 실행
finally 구문 실행
프로그램의 종료
***/
───────────────────────────────────────ⓑ

위의 예제에서 finally까지 추가된 것을 볼 수 있습니다. finally는 에러가 발생하든 하지 않든 무조건 처리되는 블록입니다. 보통의 경우 최종적으로 해주어야 하는 작업을 finally 블록에 넣으면 됩니다. 전체적인 에러처리의 순서는 다음과 같습니다.

▣ 기본적인 에러처리 순서    
◈ 1. try 블록을 일단 한번 시도한다. 
◈ 2. try 블록 내에서 에러가 발견되면 에러 이벤트가 발생한다. 
◈ 3. catch 블록에 일치하는 이벤트가 있다면 해당 catch 블록으로 간다. 
◈ 4. 해당 catch 블록을 실행해서 에러처리를 한다. (여기서 다시 try 블록으로 되돌아갈 수 없다.)
◈ 5. 에러가 발생하든 하지 않든 finally는 무조건 실행한다.

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

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

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
6.3 배열의 참조  (0) 2010/02/21
1  ... 50 51 52 53 54 55 56 57 58  ... 159 
BLOG main image
(주)KinTeL 회장 김형기

카테고리

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


Statistics Graph
Total : 73,500
Today : 11
Yesterday : 43

최근에 받은 트랙백