[자바/심화] 예외 처리(Exception Handling)

에러와 예외

에러(Error)

- 프로그램 실행 시 프로그램의 중요 기능 수행을 불가능하게 할 수 있는 문제를 의미한다.

- 하드웨어나 운영체제의 치명적인 에러, 디도스 공격과 같은 상황을 말한다.

- 에러를 해결한 후, 프로그램을 재실행해야 한다.

예외(Exception)

- 에러보다는 가벼운 문제를 말하며, 프로그램 자체적으로 문제 해결이 가능하다.

- 개발자가 프로그램을 개발하고 충분히 테스트를 한 후, 배포를 하더라도 예상치 못한 버그(Bug)가 발생되는 경우도 해당된다.

- 정상적이지 않는 케이스의 예

    - 0 으로 숫자를 나누는 경우

    - 배열의 인덱스를 초과하여 참조하고자 하는 경우

    - 파일을 열 때, 없는 파일을 열려고 하는 경우

예외 처리 목적

- 예외가 발생하면 예외가 발생하면 예외가 발생한 부분 외 다른 기능은 정상적으로 동작하도록 하기 위함에 있다.

- 자바 프로그램 실행 시에 예외가 발생하면 JVM(Java Virtual Machine)이 자체적으로 해당 예외에 대한 객체를 생성한 후, 프로그램에 전달해서 처리한다.

예외 처리 방법

- 프로그램 실행 시 발생하는 예외들을 각각의 Exception 클래스로 제공한다.

- 해당 예외가 발생하면 JVM이 해당 예외에 대한 클래스 객체를 만들어서 프로그램으로 전달한다.

- 개발자는 해당 예외 객체를 받아서 예외 처리를 한다.

Exception 클래스

생성자

생성자 설명
  Exception()   예외 메시지 없이 객체를 생성한다.
  Exception(String message)   예외 생성 시 예외 메시지를 예외 객체에 전달한다.
  Exception(String message, Throwable cause)   예외 생성 시 예외 메시지와 원인을 예외 객체에 전달한다.

메소드

- 예외 발생 시 예외 원인과 발생 위치를 쉽게 찾는 데 도움을 주므로, 디버깅 시 많이 사용된다.

- 자주 사용 되는 메소드

메소드 설명
  public String getMessage()   예외 발생 시 전달된 메시지를 리턴한다.
  public Throwable getCause()   예외 발생 시 전달된 예외 원인을 리한다.
  public void printStackTrace()   예외 발생 시 예외 발생 이력(backtrace)를 출력한다.
  예외 발생 이력은 현재 활성 상태인 함수 호출 목록을 의미한다.

try ~ catch 문

try {
    // 예외 발생 코드
} catch (예외클래스 변수) {
    // 예외 처리 코드
}
// 예외와 관련 없는 코드

- try ~ catch 문으로 예외 처리를 하면 실행 중 예외가 발생하더라도 프로그램은 정상적으로 수행 후 종료될 수 있다.

- 예외 처리 과정

    1. 예외가 발생하면 JVM에게 예외를 던진다.

    2. JVM은 발생한 예외를 분석한 후, 해당하는 예외 객체를 생성하고 예외가 발생한 곳으로 돌려준다.

    3. JVM이 던진 예외를 catch 문에서 받아서 예외를 처리한다.

    4. 예외를 처리한 후, 이후 코드는 정상적으로 실행된다.

try ~ catch ~ finally 문

try {
    // 예외 발생 코드
} catch (예외클래스 변수) {
    // 예외 처리 코드
} finally {
    // 실행 코드
}
// 예외와 관련 없는 코드

- catch 문 다음에 위치하여 예외 발생 유무에 상관없이 반드시 실행해야하는 기능을 담당한다.

- 입출력장치 등과 같이 외부 장치 연동 후 마무리 작업시 주로 사용된다.

throws와 throw 키워드를 이용한 예외 처리

throws를 이용한 예외 처리 방법

public 리턴타입 메소드명(매개변수) throws 예외클래스1 {
    ....
}

- 예외 발생시 발생한 예외를 메소드를 호출한 곳으로 떠넘겨서 예외를 처리한다.

- 최초로 메소드를 호출한 main() 메소드에서는 try ~ catch 문으로 예외 처리를 해야 한다.

- RuntimeException의 하위 클래스들은 throws를 생략해도 된다.

- 메소드의 코드가 복잡할 경우 예외를 호출한 곳으로 떠넘김으로써 가독성을 높일 수 있다.

throw 키워드를 이용한 예외 처리 방법

throw new 예외클래스생성자(매개변수);

- 예외 발생 상황이 아니더라도 필요에 의해 강제로 예외를 발생시키는 기능을 말한다.

- 사용자 정의 예외 클래스로 예외 처리를 할 때 사용된다.

- 특정 예외 발생 시 빠른 디버깅을 위해서 예외 메시지를 미리 세팅해서 사용할 수 있다.

사용자 정의 예외 클래스

public class 예외클래스명 extends Exception {
    public 예외클래스명() { }
    
    public 예외클래스명(String message) { 
        super(message);
    };
}

- 개발자가 프로그램에서 특정한 상황 발생 시 예외로 인식하기 위해 만든 클래스를 말한다.

- 일반 예외인 경우 Exception 클래스를 상속 받고, 실행 예외인 경우 RuntimeExcetion 클래스를 상속받아 만든다.

- 사용자 정의 클래스 이름은 "XXXException"과 같이 의미를 잘 나타낼 수 있도록 짓는 것이 일반적이다.

- 예외 발생 시 생성자로 예외에 대한 메시지를 전달한 후 try/catch 문에서 처리하는 것이 일반적이다.

다중 try ~ catch 문의 일반적인 형식

try {
    ...
} catch(사용자정의예외클래스 ex) {
    ...
} catch(주의할예외클래스 ex) {
    ...
} catch(Exception ex) {
    ...
}

예제

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.FileNotFoundException;

class NotTenException extends RuntimeException {}

public class Main {
    public static boolean checkTen(int ten) {
        if (ten != 10) {
            return false;
        }
        return true;
    }

    public static boolean checkTenWithException(int ten) {
        try {
            if (ten != 10) {
                throw new NotTenException();
            }
        } catch (NotTenException e) {
            System.out.println("e = " + e);
            return false;
        }
        return true;
    }

    public static boolean checkTenWithThrows(int ten) throws NotTenException {
        if (ten != 10) {
            throw new NotTenException();
        }
        return true;
    }

    public static void main(String[] args) throws IOException {
        System.out.println("== 0으로 나누기 ==");
        int a = 0;
        try {
            a = 5 / 0;
        } catch (ArithmeticException e) {
            System.out.println("0으로 나누기 예외 발생");
            System.out.println("e = " + e);
        } finally {
            System.out.println("== 0으로 나누기 종료 ==");
        }

        System.out.println("== 배열 인덱스 초과 ==");
        int[] b = new int[4];
        try {
            b[4] = 1;
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("인덱스 초과!");
            System.out.println("e = " + e);
        }
        
        System.out.println("== 없는 파일 열기 ==");
        try {
            BufferedReader br = new BufferedReader(new FileReader("abc.txt"));
        } catch (FileNotFoundException e) {
            System.out.println("abc.txt 파일이 없습니다.");
            System.out.println("e = " + e);
        }

        System.out.println("== checkTen ==");
        boolean checkResult = Main.checkTen(10);
        System.out.println("checkResult = " + checkResult);

        System.out.println("== checkTenWithException ==");
        checkResult = checkTenWithException(5);
        System.out.println("checkResult = " + checkResult);

        System.out.println("== checkTenWithThrows ==");
        try {
            checkResult = checkTenWithThrows(5);
        } catch (NotTenException e) {
            System.out.println("e = " + e);
        }
        System.out.println("checkResult = " + checkResult);
    }
}

/*
    << 결과 예시 >>
    == 0으로 나누기 ==
    0으로 나누기 예외 발생
    e = java.lang.ArithmeticException: / by zero
    == 0으로 나누기 종료 ==
    == 배열 인덱스 초과 ==
    인덱스 초과!
    e = java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4
    == 없는 파일 열기 ==
    abc.txt 파일이 없습니다.
    e = java.io.FileNotFoundException: abc.txt (No such file or directory)
    == checkTen ==
    checkResult = true
    == checkTenWithException ==
    e = NotTenException
    checkResult = false
    == checkTenWithThrows ==
    e = NotTenException
    checkResult = false
*/

⊙ 참고 문헌

  1. 이병승, 「초보 개발자를 위한 자바:한 권으로 배우는 자바 마스터 가이드 북」, 영진닷컴, 2024, p657 - 690
  2. 마종현, 「제로베이스 백엔드 취업 파트타임 스쿨 5기:Part 01. Java 기초-Chapter 01. Java 프로그래밍-14.예외 처리」, 제로베이스, 2024, https://zero-base.co.kr/