언어(Language)/Java

[Java] 자바 예외 처리(Exception Handling) 개념 정리 및 활용

잇트루 2022. 9. 20. 04:00
반응형

Intro

프로그래밍을 하고 실행하면 수많은 에러와 예외를 마주하게 된다. 에러 또는 예외가 발생하게 되면 프로그램은 의도한 대로 작동하지 않거나 실행을 비정상적으로 종료하기도 한다.

이러한 에러와 예외들이 발생할 가능성이 있을 때, 효과적으로 처리하는 방법이 바로 예외 처리이다.

 

예외 처리란? (Exception Handling)

예외 처리는 코드 작성자가 예기치 않게 발생하는 에러들에 대응할 수 도록 사전에 방지하는 것이다. 예외 처리를 하면 프로그램의 비정상적인 종료를 방지하여 정상적인 실행 상태를 유지할 수 있다.

 

프로그램에서 에러가 발생하는 이유

에러가 발생하는 원인은 수없이 다양하다. 하지만 자주 발생하는 에러의 몇 가지 예시는 다음과 같다.

사용자의 입력 실수

네트워크 연결 끊김

메모리 공간 부족

개발자의 코드 실수

유효하지 않는 파일 사용

즉, 에러가 발생하는 원인은 크게 내부적인 요인과 외부적인 요인이 있다.

외부적인 요인에는 입력 에러, 하드웨어 문제, 네트워크 연결 에러 등이 있으며, 내부적인 요인으로는 개발자의 코드 작성에 있다.

 

여기서 중점적으로 파악해야 할 부분은 개발자의 코드 작성이다.

예를 들어, 실제로 존재하지 않는 파일을 불러오려 한다면, FileNotFoundException이 발생할 것이다.

java: unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown

 

또한, 배열의 범위를 벗어나는 값을 불러오려 한다면, ArrayIndexOutOfBoundsException이 발생할 것이다.

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at ErrorTest.main(ErrorTest.java:4)

이처럼 코드를 작성하다 보면 다양한 예외를 접하게 될 것이다.

 

FileNotFoundException와 ArrayIndexOutOfBoundsException에서도 차이가 존재한다.

FileNotFoundException의 경우 코드를 실행하기 전에 에러가 발생하고,

ArrayIndexOutOfBoundsException의 경우 코드를 실행하다가 예외가 존재하는 시점에서 에러가 발생한다.

 

이처럼 자바에서는 에러가 발생하는 시점에 따라 컴파일 에러(Compile Time Error)와 런타임 에러(Run Time Error)로 구분하고 있다.

 

에러와 예외 (Error and Exception)

넓은 의미에서는 프로그램 실행 시 발생할 수 있는 모든 문제들을 에러라 부르기도 하지만, 에러와 예외는 프로그래밍에서 엄밀히 말하면 차이가 있다.

특히, 코드 실행(run-time) 시 발생할 수 있는 문제들을 크게 에러와 예외로 구분하고 있다.

 

에러(Error)의 경우, 한 번 발생하면 복구하기 어려운 수준의 심각한 문제를 의미하고, 대표적으로 메모리 부족인 OutOfMemoryError와 스택 오버플로우(StackOverflowError)가 있다.

 

예외(Exception)는 개발자의 잘못된 사용으로 인해 발생하는 에러는 상대적으로 약한 문제의 수준을 말한다. 즉, 개발자의 실수로 인해 발생하는 것이다. 이는 코드 수정을 통해 수습이 가능한 문제이다.

 

따라서 예외 처리는 코드 수정으로 해결이 가능한 문제를 처리하는 것이다.

 

예외 클래스의 상속 계층도

자바에서는 예외가 발생하면 예외 클래스로부터 객체를 생성하여 해당 인스턴스를 통해 예외처리를 한다.

자바의 모든 에러와 예외 클래스는 Throwable 클래스로부터 확장되며, 모든 예외(Exception)의 상위 클래스는 Exception 클래스이다.

또한 Exception 클래스는 실행 예외 클래스(Runtime Exception)와 일반 예외 클래스(Other Exception)로 구분된다.

 

일반 예외 클래스(Exception)

런타임 시 발생하는 에러를 제외한 모든 Exception 클래스와 하위 클래스들을 카리 킨다.

컴파일러가 코드 실행 전에 예외 처리 코드 여부를 검사하기 때문에 Checked 예외라 부르기도 한다.

주로 잘못된 클래스명(ClassNotFoundException)이나 데이터 형식(DataFormatException) 등 사용자 편의 실수로 발생하는 경우가 많다.

 

실행 예외 클래스(Runtime Exception)

런타임 시 발생하는 RuntimeException 클래스와 하위 클래스들을 카리 킨다.

컴파일러가 예외 처리 코드 여부를 검사하지 않기 때문에 Unchecked 예외라 부르기도 한다.

클래스 간의 형 변환 오류(ClassCastException), 배열의 범위를 벗어난 오류(ArrayIndexOutOfBoundsException), 값이 null인 참조 변수를 사용할 때 발생하는 오류(NullPointerException) 등이 있다.

 

try - catch문

이제 잠재적으로 발생할 수 있는 에러에 대비하여 실행 상태를 유지시킬 수 있는 예외 처리 방법에 대해서 알아본다. 자바에서는 try - catch 문법을 활용하여 예외 처리를 구현할 수 있다.

또한, 부가적으로 finally 블록을 통해 예외 발생 여부와 상관없이 항상 실행할 수 있도록 할 수도 있다.

try {
    // 예외가 발생할 가능성이 있는 코드
} catch (ExceptionType1 e1) {
    // ExceptionType1 예외가 발생했을 경우 실행할 코드
} catch (ExceptionType2 e2) {
    // ExceptionType2 예외가 발생했을 경우 실행할 코드
} finally {
    // 예외 발생 여부와 상관없이 항상 실행
}

try

try 블록 안에 예외가 발생할 가능성이 있는 코드를 작성한다. 만약, 작성한 코드가 예외 없이 정상적으로 동작하면 catch 블록은 실행되지 않고, 예외처리를 종료하거나 finally 블록이 실행된다.

 

catch

try 블록에서 예외가 발생할 경우 실행되는 코드 블록이다. 여러 종류의 예외 처리가 가능하다. 괄호() 안에 Exception 클래스를 작성한다면, 모든 예외에 대하여 처리가 가능하기도 하며, 특정 예외에서만 처리하고 싶다면 괄호() 안에 특정 예외에 대한 클래스를 작성한다.

catch 블록이 여러 개인 경우, 일치하는 하나의 catch 블록만 실행되고 예외처리를 종료하거나 finally 블록으로 넘어가게 된다.

 

finally

finally 블록은 옵션으로 꼭 작성해야 하는 것은 아니다. 만약 finally 블록을 작성한다면, 예외 발생 여부와는 상관없이 항상 실행한다.

 

try - catch 예제

만약, 문자열을 입력받아 대문자로 변환하여 출력해주는 코드에서 null 값을 넣게 된다면, NullPointerException 예외가 발생하게 된다.

public class ExceptionTest {
    static void printStr(String str) {
        String upperStr = str.toUpperCase();
        System.out.println(upperStr);
    }

    public static void main(String[] args) {
        printStr(null);
    }
}
Exception in thread "main" java.lang.NullPointerException
    at Section1.exceptionHandling.ExceptionTest.printStr(ExceptionTest.java:6)
    at Section1.exceptionHandling.ExceptionTest.main(ExceptionTest.java:11)

 

이를 try-catch 문을 활용하여 발생하는 예외를 처리할 수 있다.

public class ExceptionTest {
    static void printStr(String str) {
        String upperStr = str.toUpperCase();
        System.out.println(upperStr);
    }

    public static void main(String[] args) {
        try {
            printStr("Hello");
            printStr(null);
        } catch (NullPointerException e) {
            System.out.println("예외 발생");
        } finally {
            System.out.println("finally 실행");
        }
    }
}
// 출력
HELLO
예외 발생
finally 실행

 

또한, catch에서 괄호 안의 e와 getMessage(), toString() 메서드를 이용하여 예외에 대한 정보를 얻을 수 있다.

public class ExceptionTest {
    static void printStr(String str) {
        String upperStr = str.toUpperCase();
        System.out.println(upperStr);
    }

    public static void main(String[] args) {
        try {
            printStr(null);
        } catch (NullPointerException e) {
            System.out.println(e.getMessage());
            System.out.println(e.toString());
        } finally {
            System.out.println("finally 실행");
        }
    }
}
// 출력
null
java.lang.NullPointerException
finally 실행

위 예제를 분석하면,

프로그램이 실행되는 도중에 null 값을 printStr 메서드에 넘기면서 호출되는 부분에서 예외가 발생한다.

발생한 예외는 NullPointerException으로 catch 문의 조건에서 걸린 예외에 대하여 catch 블록의 코드를 실행한다. 예외 발생 시 검사는 instanceOf 연산자를 통해 검사한다.

이후 finally 블록을 실행하여 해당 내용인 ‘finally 실행’ 문자열을 출력한다.

finally의 경우 굳이 작성하지 않아도 된다.

 

예외 처리 시 주의할 점

catch 문은 if문과 비슷하게 순차적으로 검사를 진행한다. 따라서, 상위 catch 블록에서 예외 처리를 하게 되면 하위에 존재하는 catch문은 실행되지 않는다.

따라서, 여러 개의 catch 블록을 작성할 경우, 예외 클래스의 하위 클래스를 먼저 배치하여 구체적인 예외처리를 먼저 검사하도록 작성하는 것이 좋다.

 

예외 전가

예외 전가는 try-catch 문 외에 예외를 호출한 곳으로 다시 예외를 떠넘기는 것을 말한다.

메서드의 선언부 끝에 throws 키워드를 작성하여 발생할 수 있는 예외들을 쉼표로 구분하여 나열하는 것으로 작성한다.

void methodEx() throws NullPointerException, ArrayIndexOutOfBoundsException, ... {

}

throws를 사용하면 자바 JVM이 최종적으로 예외의 내용을 콘솔에 출력하여 예외처리를 수행한다.

public class ThrowsException {
    public static void main(String[] args) {
        try {
            throwException();
        } catch (ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }
    }

    static void throwException() throws ClassNotFoundException, NullPointerException {
        Class.forName("예외 발생");
    }
}
// 출력
예외 발생

위 코드의 경우, throws 키워드를 사용하여 해당 예외를 발생한 메서드 안에서 처리하는 것이 아닌, 메서드를 호출한 곳을 다시 떠넘긴다. 따라서 예외 처리 책임은 throwException 메서드가 아닌 main 메서드가 지게 된다.

 

예외를 의도적으로 발생

throws 키워드와 유사한 throw 키워드를 사용하면, 의도적으로 예외를 발생시킬 수 있다.

public class ThrowException {
    public static void main(String[] args) {
        try {
            Exception intendedException = new Exception("의도적인 예외");
            throw intendedException;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
// 출력
의도적인 예외

예기치 않게 발생할 수 있는 에러에 대응할 수 있도록 사전에 코드를 작성하여 예외 처리를 할 수 있다.

반응형