hanker

Java - 예외 처리의 활용 및 효율적인 관리 방법 (Custom Exception, Exception Propagation) 본문

JAVA

Java - 예외 처리의 활용 및 효율적인 관리 방법 (Custom Exception, Exception Propagation)

hanker 2025. 2. 10. 00:00
반응형

https://hanke-r.tistory.com/entry/Java-CheckedException%EA%B3%BC-UncheckedException%EC%9D%B4-%EB%AD%90%EC%A7%80

 

이전에 예외처리 관련해서 글을 작성했었는데, 해당 내용만으로는 실제 활용하기에는 어려움이 있어, 보다 더 자세하게 예외처리에 대해서 작성해 본다.

 

이번 글에서는 예외처리를 어떻게 사용하는지와 효율적으로 관리하는 방법에 대해서 알아보자.

 


1. 예외의 종류와 특징

 

자바의 예외는 크게 Checked Exception과 Unchecked Exception으로 나뉜다.

위 링크에 상세하게 설명해 있으니 간단하게만 작성하고 넘어가도록 하자.

 

1-1. Checked Exception

- 컴파일러가 예외 처리를 강제

- 반드시 try-catch문을 사용하거나 throws 키워드로 처리해야 함

- 파일 입출력, 네트워크 작업, 데이터베이스 연결 등 외부 자원과 관련된 예외가 많음

import java.io.*;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        try {
            FileReader file = new FileReader("non_existing_file.txt");
            BufferedReader br = new BufferedReader(file);
            System.out.println(br.readLine());
        } catch (IOException e) {
            System.out.println("파일을 읽을 수 없습니다: " + e.getMessage());
        }
    }
}

- 이렇게 try - catch문으로 감싸주지 않으면 컴파일이 안된다.

 

 

1-2. Unchecked Exception

- RuntimeException을 상속받은 예외

- 컴파일러가 예외 처리를 강제하지 않는다.

- 주로 프로그래밍 오류 (Null, 배열 인덱스 초과, 잘못된 형 변형)와 관련되어 있다.

public class UncheckedExceptionExample {
    public static void main(String[] args) {
        String str = null;
        System.out.println(str.length()); // NullPointerException 발생
    }
}

- 컴파일러가 NullPointerException을 강제하지 않고, 런타임에서 발생한다.

 

 


2. 사용자 정의 예외 (Custom Exception)

 

실무에서 자바의 기본 예외클래스만 사용하는 것이 아닌, 로직에 맞게 예외를 직접 정의해서 사용하는 경우가 있다. 

아래 예제를 통해 Checked Exception과 Unchecked Exception을 Custom 해서 사용해 보자.

 

2-1. Checked Exception Custom

- score 범위를 정하고, 범위를 벗어나면 Checked Exception을 발생시키는 예제이다.

 

InvalidScoreException.java

// 사용자 정의 Checked Exception
public class InvalidScoreException extends Exception {
    // 기본 생성자(필요시 추가 가능)
    public InvalidScoreException() {
        super();
    }
    // 메시지를 전달받는 생성자
    public InvalidScoreException(String message) {
        super(message);
    }
}

 

Main.java

public class Main {
    // 점수를 검증하는 메서드: 점수가 0미만이거나 100초과이면 예외 발생
    public static void checkScore(int score) throws InvalidScoreException {
        if (score < 0 || score > 100) {
            throw new InvalidScoreException("점수는 0 이상 100 이하여야 합니다. 입력된 값: " + score);
        }
    }

    public static void main(String[] args) {
        int score = -10;  // 잘못된 점수

        // Checked Exception이므로 try-catch나 throws로 처리해야 함
        try {
            checkScore(score);
            System.out.println("입력한 점수는 " + score + "입니다.");
        } catch (InvalidScoreException e) {
            System.out.println("Checked Exception 처리: " + e.getMessage());
        }
    }
}

 

- InvalidScoreException은 Exception을 상속받아 checked exception으로 정의되었으므로, checkedScore 메서드에서는 throws를 선언하고, 호출하는 main메서드에서 반드시 try-catch로 처리해야 한다.

- 점수 값이 잘못되었을 경우 예외 메시지가 출력된다.

 

 

2-2. Unchecked Exception Custom

- 은행 계좌에서 출금할 때 잔액 부족으로 인한 Unchecked Exception 처리

 

InsufficientFundsException.java

public class InsufficientFundsException extends RuntimeException {
    // 기본 생성자
    public InsufficientFundsException() {
        super();
    }
    // 메시지를 전달받는 생성자
    public InsufficientFundsException(String message) {
        super(message);
    }
}

 

BankAccount.java

import org.example.exception.InsufficientFundsException;

// 은행 계좌 클래스
public class BankAccount {
    private double balance;

    public BankAccount(double balance) {
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }

    // 잔고가 부족하면 Unchecked Exception(InsufficientFundsException)을 throw
    public void withdraw(double amount) {
        if (amount > balance) {
            throw new InsufficientFundsException("출금액이 잔고보다 큽니다. 부족한 금액: " + (amount - balance));
        }
        balance -= amount;
    }
}

 

Main.java

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

        // Unchecked Exception은 try-catch를 사용하지 않아도 컴파일되지만,
        // 여기서는 예외 메시지를 출력하기 위해 try-catch 사용
        try {
            account.withdraw(1500.0);
            System.out.println("출금 후 잔고: " + account.getBalance());
        } catch (InsufficientFundsException e) {
            System.out.println("Unchecked Exception 처리: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

- InsufficientFundsException은 RuntimeException을 상속받아 Unchecked Exception으로 정의되었다.

BankAccount.java 내 withdraw 메서드에서 잔고가 부족한 경우 예외를 throw 하며, 호출한 곳에서는 선택적으로 try-catch로 처리한다.

- try-catch가 없더라도 컴파일은 되지만, 런타임 시 예외가 발생하면 프로그램이 종료되므로 적절한 예외 처리가 필요하다.

 


3. 예외 전파 (Exception Propagation)

 

예외가 한 메서드에서 발생하면, 호출한 메서드로 전파될 수 있다.

즉, 메서드 내에서 발생한 예외가 해당 메서드에서 직접 처리되지 않을 경우, 그 예외가 호출한 상위 메서드로 전달되어 결국 프로그램의 최상위 호출 스택까지 도달하는 과정을 의미한다.

 

public class PropagationExample {

    // methodB에서 발생한 예외를 직접 처리하지 않고 전파함
    public static void methodB() throws Exception {
        // 예외 발생
        throw new Exception("methodB에서 예외 발생");
    }

    // methodA는 methodB가 던지는 예외를 처리하지 않고 throws로 위임
    public static void methodA() throws Exception {
        methodB();
    }

    public static void main(String[] args) {
        try {
            methodA();
        } catch (Exception e) {
            System.out.println("예외가 main에서 처리됨: " + e.getMessage());
        }
    }
}

- 위 코드를 보면 methodB()에서 예외가 발생했지만, methodA()를 지나, main() 메서드의 try-catch 블록에서 잡혀 처리된다.

 

왜 예외 전파를 사용할까?

예외 전파를 사용하면 예외를 발생시킨 하위 메서드에서 세부적인 로직을 작성할 필요 없이, 호출 측에서 한 번에 처리할 수 있어 코드의 가독성이 향상된다. 또한, 여러 메서드가 관련된 복잡한 호출 스택에서 공통의 예외 처리 로직을 적용할 수 있다.

 


4. try-with-resources 문법과 자동 리소스 관리

 

java7에서 도입된 기능으로, 리소스(파일, 데이터베이스 연결, 소켓 등)를 사용한 후 명시적으로 close() 메서드를 호출해 닫아 주어야 하는 번거로움을 줄여준다.

 

이 구문은 try 블록의 괄호 안에 AutoCloseable 또는 Closeable 인터페이스를 구현한 리소스를 선언하면, try 블록이 끝날 때 자동으로 close() 메서드를 호출하여 리소스를 close 해준다.

 

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

public class TryWithResourcesExample {
    public static void main(String[] args) {
        // "data.txt" 파일을 읽어 한 줄씩 출력하는 예제
        try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("파일 입출력 오류: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

- try 괄호 내에 리소스를 선언하면, try블록이 종료될 때 자동으로 close()가 호출되어 해제된다.

- try 블록 내에서 예외가 발생하더라도, 선언된 자원은 자동으로 닫히므로 리소스 누수를 방지할 수 있다.

 


정리

 

예외 처리는 프로그램의 안정성을 위해 필수적이다.

Checked Excpetion, Unchecked Exception의 특징을 파악하여 적절하게 사용하고, Custom Exception을 사용하여 의미 있는 예외를 정의해 보자.

또한 예외 전파를 활용하여 코드의 가독성을 높여보자.

 

끝.

 

반응형