자바 예외 처리(try and catch) | 자바 입문강좌 54

자바 예외 처리(try and catch)

왜 하는가? 예외? 오류?

흔히 컴퓨터가 뻑난다 라는 표현을 사용합니다. 컴퓨터가 고장났을 때 사용하는 말이죠.

그리고 분명 운영체제는 잘 돌아가는데 소프트웨어 적으로 오류가 나서 응용 프로그램(Application)이 실행되지 않는 경우도 있습니다. 이런 경우 Crash 또는 Error 라고 합니다.

또는 프로그램을 시작은 잘 했는데 중간에 오류가 나서 종료되는 경우도 있습니다. 원인은 여러가지 있겠지만 프로그램 실행도중 종료되는 경우는 많은 경우 예외(Exception)에 해당합니다.

그러니까 컴퓨터 오류 = 컴퓨터가 뻑난다고 그동안 생각해왔다면 생각을 바꿀 필요가 있습니다. 컴퓨터의 작동과정 중에 어디에서 멈추는가 – 어느 부분에서 오류가 발생하는가 – 정지하는가 등 단계와 상황에 따라전혀 다르게 접근해야할 문제이기 때문입니다.

자바에서 다루는 예외는 Throwable 클래스의 서브클래스로 Exception 클래스에서 처리합니다.

예외를 언어의 오류 측면에서 보면 자바의 오류는 Compile 오류와 Runtime 오류가 있습니다. Compile 오류는 소스코드를 컴파일하는 단계에서 걸러지기 때문에 이를 위한 별도의 코드를 만들 필요는 없습니다. 컴파을 잘하면 되죠. 그런데 실행도중 오류인 Runtime 오류는 돌발상황이 생기기 쉽습니다. 컴파일이 사전적인 준비라면 런타임은 실전적인 대응메뉴얼입니다.

사람이 아무리 연습해도 실전에서 못하는 경우가 있죠. 바로 이 예외사항을 잘 컨트롤 하는 능력이 부족하기 때문입니다.

런타임에 오류가 많이 나는 이유는 간단합니다. 통제가 안되는 변수가 많기 때문이죠. 예를 들어 사용자에게 숫자를 입력 받아서 나눗셈을 해야하는데 사용자가 0으로 나누는 것을 요구합니다. -> 런타임 나눗셈 에러죠. 사람도 컴퓨터도 숫자를 0으로 못 나눕니다. 파일을 사용해야 하는데 파일이 없거나 다른 프로그램이 쓰고 있어서 접근이 안된다. -> 런타임 파일 에러입니다.

즉 컴파일할 때는 개발단계라서 해결할 수 있는 문제들이 설치(install)되는 시스템 환경에서는 해결이 안될 수가 있습니다. 여러가지 이유로. 프로그램이란 것은 굉장히 단순하기 때문에 0으로 나누지 못하는 이유 하나만으로도 전체 코드가 종료됩니다.

그런 실수들은 언제나 일어날 수 있기 때문에 예외처리를 해주지 않으면 프로그램은 실행하고 나서 오래 버티지 못할 겁니다.

예제 배열 예외

예외처리는 곧 디버그와도 연관되기 때문에 앞에서 약간 설명을 더 했습니다. 이제 예제를 보면서 알아보겠습니다.

배열이 대표적인 예외 처리에 들어갑니다. 배열의 인덱스가 넘어가면 어떻게 될까요? 배열인덱스의 끝이 4인데 5를 사용하려고 한다고 합시다. 4바이트 정수형이면 20바이트가 배열에 할당 되어있는데 프로그램이 24바이트를 사용하려 합니다.

이런 것을 명백히 선이 넘었다고 하는 겁니다. 20바이트만 쓰라고 허가해 줬는데 24바이트를 쓰려고 합니다. 이런 경우 자바 프로그램은 이것 하나때문에 종료를 시킵니다. 이유는 선을 넘었기 때문입니다.

그런데 이런 경우에도 프로그램을 끝까지 돌릴 수 있습니다. 아니 에러메시지만 출력하고 프로그램을 하루종일 돌릴 수도 있습니다. 왜냐하면 에러처리를 해줬기 때문입니다.

ArrayIndexOutofBoundsException 은 배열인덱스가 정해진 바운드(경계선)를 넘었을 때 처리해주는 클래스입니다. 이를 구현하려면 try catch 문을 사용합니다. 다른 언어들도 비슷한 예외처리 문법을 가지고 있습니다. try { 예외가 발생할 것 같은 블록 } catch { 예외 발생 후의 처리 과정 } 에서 런타임 에러시 처리할 일들을 정의합니다.

package com.kay;

public class Main {
    public static void main(String[] args) {

        int[] myArray = {1,3,5,7,9};

        try{
            for (int i = 0; i < 6; i++) {
                System.out.print(myArray[i] + ", ");
            }
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println();
            System.out.println(e);
            System.out.println("[try and catch]");
        }
        System.out.println("<프로그램 제어>");
    }
}
1, 3, 5, 7, 9, 
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
[try and catch]
<프로그램 제어>

인덱스 길이가 5인데 인덱스 5는 경계를 넘었다는 메시지를 출력합니다. 이 메시지를 보고 프로그램의 유지보수를 할 수 있습니다. 여기서는 간단해 보이지만 실제 오류가 발생하면 이런 메시지 하나가 없어서 헤메일 수 있습니다. 그러니까 포인트는 어디서 예외가 날지 미리 예측하는데 있습니다.

경험적인 부분이지만 Exception 클래스들을 공부하다 보면 일반적인 원리를 알 수 있습니다.

finally 예제

finally 는 try-catch 문의 마지막에 항상 실행되는 코드를 넣습니다. try catch 에서 catch를 다수 사용하면 여러가지 예외를 처리할 수 있습니다. 그런데 catch 를 할 때마다 자원을 반환하는 코드를 넣는게 불편합니다. finally 는 catch 에서 공통적으로 처리해야 할 자원의 반환 (파일 닫기 등) 등 마지막(final) 코드를 작성할 수 있습니다.

아래 예제를 보면 finally 안에서도 또다시 try catch 문을 중첩할 수 있습니다. 복잡해 보이지만 원리적으로 보면 파일스트림을 닫는 것이므로 예외 처리를 해줘야 합니다.

예제에서는 파일을 찾고 있는데 없습니다. 파일이 없으니까 진행이 안되므로 예외처리를 합니다.

마찬가지로 finally 가 끝난 후의 코드도 정상적으로 실행이 됩니다

package com.kay;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {

        FileInputStream fIStream = null;
        System.out.println("-- Test 파일");

        try {
            System.out.println("파일을 엽니다");
            fIStream = new FileInputStream("test.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(fIStream != null){
                try{
                    fIStream.close();
                } catch (IOException e){
                    e.printStackTrace();
                }
            }
        }

        System.out.println("-- 파일 입력스트림 예외처리");
    }
}
-- Test 파일
파일을 엽니다
-- 파일 입력스트림 예외처리
java.io.FileNotFoundException: test.txt (지정된 파일을 찾을 수 없습니다)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:211)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:153)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:108)
	at com.kay.Main.main(Main.java:15)

AutoCloesalbe 인터페이스

AutoClosesable 인터페이스는 finally 에서 매번 반환해야 하는 고정적인 자원들을 명시적으로 반환하지 않아도 알아서 반환하도록 구현합니다.

아래는 콘솔출력에 표시만 되있지만 close메소드에 실제로 자원을 반환하는 코드를 구현해주면 됩니다.

package com.kay;

public class Main {
    public static void main(String[] args) {
        try(AutoClose obj = new AutoClose()){
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("[예외 표시]");
        }
    }
}
class AutoClose implements AutoCloseable{

    @Override
    public void close() throws Exception {
        System.out.println("자원을 클로즈 합니다");
    }
}
자원을 클로즈 합니다

강제 예외 (Throw)

테스트를 하기 위해 throw로 강제 예외 발생을 시킵니다. 예외 클래스의 base가 Throwable 이라는 것을 생각하면 에러나 오류가 나면 던진다(Throw)는 아이디어가 있습니다. 왜 던질까요? 정상적 루틴에서는 예외를 처리할 수 없으니까 이것을 처리할 수 있는 클래스에게 던지는 것입니다. new 키워드를 사용하는 것으로 보면 예외처리를 위해 Exception 인스턴스를 생성한 다는 것을 알 수 있습니다.

        try(AutoClose obj = new AutoClose()){
            throw new Exception();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("[예외 표시]");
        }
자원을 클로즈 합니다
[예외 표시]
java.lang.Exception
	at com.kay.Main.main(Main.java:6)

예외 처리 지연 throws 키워드

예외 처리를 하는 방법은 여러가지가 있습니다. 이클립스나 인텔리제이의 자동완성 옵션을 사용해서 보면 Exception 에 대한 세가지 처리방법을 볼 수 있습니다. throws 키워드를 main 함수나 클래스 헤드에 정의하면 예외처리를 지연시킬 수 있습니다.

이 말은 어떤 클래스나 메소드는 예외처리 없이 사용할 수 없습니다. try catch 로 잡아야 한다는 말인데 그러지 말고 그냥 퉁쳐서 클래스 헤드에 throw 로 집어넣는 방식입니다. 컴파일시 오류가 나지 않지만 예외발생시 퉁쳐서 나옵니다. 그러므로 정식적인 프로그램에는 try catch 를 사용하는게 좋습니다.

다시 말하면 Exception 처리를 안하면 컴파일이 불가능한 것들이 있습니다. 이 때 어떻게 예외를 처리할 것이냐 자바에서 옵션을 준겁니다. try-catch 를 사용해서 정교한 오류처리를 하는 것이 바람직한 방법이죠.

package com.kay;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class Main {
    public static void main(String[] args) {
        Main m1 = new Main();

        System.out.println("프로그램 시작");
        try {
            m1.loadClass("test.txt", "java.lang.String");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
//            default exception
        } catch (Exception e){
            e.printStackTrace();
        }

        System.out.println("프로그램의 끝에 도달");
    }

    public Class loadClass(String fileName, String className)
            throws FileNotFoundException, ClassNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(fileName);
        Class c1 = Class.forName(className);
        return c1;
    }
}
프로그램 시작
프로그램의 끝에 도달
java.io.FileNotFoundException: test.txt (지정된 파일을 찾을 수 없습니다)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:211)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:153)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:108)
	at com.kay.Main.loadClass(Main.java:27)
	at com.kay.Main.main(Main.java:12)

요약

이번 포스팅은 자바 예외 처리 코드들을 살펴봤습니다. 소프트웨어 안에서는 수많은 오류들이 생길 수 있고 사소한 오류로도 프로그램은 진행이 정지될 수 있습니다. 어느정도 예측을 해서 예외사항을 처리해주면 프로그램은 훨씬 원활하게 돌아갈 것이고 디버그도 쉬워질 것 입니다.

참고문서

Java – Exceptions – Tutorialspoint

Lesson: Exceptions (The Java™ Tutorials > Essential Classes) (oracle.com)

Java Exceptions (Try…Catch) (w3schools.com)

Exception Handling in Java | Baeldung

Exception Handling in Java | Java Exceptions – javatpoint

Java Exception Handling (자바 예외 처리) (jenkov.com)

Exception handling in java with examples (beginnersbook.com)

Exceptions in Java – GeeksforGeeks

Java Exception Handling (With Examples) (programiz.com)

자바 예외 처리 | try-catch 문

Leave a Comment