예외 처리

sungs·2025년 7월 4일

자바

목록 보기
29/95

예외 처리

  • 자바에서도 예상치 못한 문제 상황, 즉 예외가 발생할 수 있는데 이를 처리하는 것을 예외 처리라고 한다.
  • 프로그램의 안정성, 오류 분석 및 디버깅 용이, 사용자 경험 개선 등 여러 가지 이유로 반드시 해야 한다.

예외가 발생할 수 있는 상황

1. 예측 불가능한 외부와 상호작용할 때

데이터 입/출력, 네트워크 통신, 데이터 통신, 외부 api 호출 등 이러한 예측하기 힘든 외부와 상호작용할 때는 예외를 처리해주어야 한다.

2. 개발자의 실수

프로그래밍을 하는 과정에서 실수로 인해 예외가 발생할 수도 있다. 물론 이는 최대한 예방하는 게 좋다.

3. 비즈니스 로직에서의 예외

데이터 무결성 위반, 권한 부족, 재고 부족 등으로 더이상 정상적인 처리가 발생했을 때 예외를 처리한다.

4. 사용자 입력이나 환경 설정이 잘못됐을 때

사용자 입력이 잘못되었을 때도 발생할 수 있다. 예를 들어 조건에 맞지 않는 문장이나 숫자를 넣거나 돈이 없는데도 출금을 시도하는 경우 등 이럴 때는 예외를 처리해주어야 한다. 그리고 프로그램의 설정 파일이 유효하지 않을 떄도 처리해주어야 한다.

정리

예외 처리는 주로 외부와 상호작용하거나 잘못된 입력이나 실수로 프로그램이 더이상 정상적인 처리를 할 수 없을 때 발생할 때 사용해야 한다. 혹은 복구 로직이 확실히 있거나 사용자에게 예외가 발생했다는 걸 알릴 때도 사용할 수 있다.

예외의 종류

1. checked Exception

  • 컴파일 때 발생하는 오류.
  • 반드시 처리해줘야 하는 오류로, try-catch나 throw로 처리를 해줘야 한다.
  • ex IOExepction, SQLException

2. unchecked Exception(RuntimeException 및 하위)

  • 런타임 때 발생하는 오류.
  • 컴파일러 강제하지는 않는 예외로, 주로 상위 계층이나 호출하는 쪽에 던져서 처리한다. 개발자가 명시적으로 처리하지 않아도 되나 런타임 때 오류가 발생할 수 있다.
  • ex NullPointerException, ArrayIndexOutOfBoundsException

예외 래핑

요새는 checked Exception도 unchecked Exception으로 씌워서 처리하는 경우가 많다. 물론 명확히 처리할 수 있으면 checked Exception을 처리해도 되지만, 씌우는 쪽이 좀 더 중앙화하여 처리할 수 있고 코드가 좀 더 간결해진다. 게다가 checked Exception인 경우 과도한 try-catch 블록, api 변경이 어려워짐, 낮은 복구 가능성이라는 단점이 있기 때문에 이를 피하려고 씌우는 경우가 많다.

예외 처리 구문

1. try-catch 구문

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

public class ExceptionHandlingExample {

    public static void main(String[] args) {
        // 1. try-catch 블록을 사용한 예외 처리
        try {
            int result = 10 / 0; // ArithmeticException 발생
            System.out.println("Result: " + result); // 이 코드는 실행되지 않음
        } catch (ArithmeticException e) {
            System.err.println("0으로 나눌 수 없습니다: " + e.getMessage());
        } catch (Exception e) { // 더 일반적인 예외는 아래쪽에 작성 (다형성)
            System.err.println("예상치 못한 오류가 발생했습니다: " + e.getMessage());
        } finally {
            System.out.println("try-catch 블록 실행 완료 (항상 실행됩니다).");
        }

        System.out.println("------------------------------------");

        // 2. Checked Exception 처리 예시 (IOException)
        FileReader fr = null;
        try {
            fr = new FileReader("nonExistentFile.txt"); // FileNotFoundException (IOException의 하위 클래스)
            int data;
            while ((data = fr.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            System.err.println("파일을 읽는 도중 오류 발생: " + e.getMessage());
        } finally {
            if (fr != null) {
                try {
                    fr.close(); // 자원 해제 (닫을 때도 예외 발생 가능성이 있어 try-catch 필요)
                    System.out.println("파일 리더가 닫혔습니다.");
                } catch (IOException e) {
                    System.err.println("파일 리더를 닫는 도중 오류 발생: " + e.getMessage());
                }
            }
        }

        System.out.println("------------------------------------");

        // 3. throws를 사용한 예외 전파 예시
        try {
            divide(10, 0);
        } catch (ArithmeticException e) {
            System.err.println("divide 메서드에서 예외 발생: " + e.getMessage());
        }

        System.out.println("프로그램 종료.");
    }

    // throws 키워드를 사용하여 ArithmeticException을 호출한 쪽으로 던짐
    public static void divide(int a, int b) throws ArithmeticException {
        if (b == 0) {
            throw new ArithmeticException("0으로 나눌 수 없습니다."); // 직접 예외 발생
        }
        System.out.println("나눗셈 결과: " + (a / b));
    }
}
  • try에 예외가 발생할 것 같은 코드들을 집어넣고 catch에서 해결한다.
  • 여기서 finally는 catch 잡든 못 잡았든 항상 실행된다.

2. try-with-resources

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

public class TryWithResourcesExample {
    public static void main(String[] args) {
        // try-with-resources를 사용하여 FileReader를 자동으로 닫음
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("파일 처리 중 오류 발생: " + e.getMessage());
        }
    }
}
  • finally에서 자원을 안 닫아도 자동으로 닫는다.
  • try 괄호 안에 닫아야 할 자원을 넣는다.

3. 사용자 정의 예외

// Checked Exception으로 사용자 정의 예외 생성
class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

public class CustomExceptionExample {
    private static double balance = 1000;

    public static void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("잔액이 부족합니다. 현재 잔액: " + balance + "원");
        }
        balance -= amount;
        System.out.println(amount + "원 인출 완료. 남은 잔액: " + balance + "원");
    }

    public static void main(String[] args) {
        try {
            withdraw(500);
            withdraw(700); // InsufficientFundsException 발생
        } catch (InsufficientFundsException e) {
            System.err.println("인출 실패: " + e.getMessage());
        }
    }
}
  • 특정 비즈니스 로직에 맞게 사용자가 직접 에외를 정의해서 사용할 수 있다.

4. throw / throws

import java.io.File;
import java.io.IOException;

public class ThrowExample {

    // 이 메서드는 파일을 읽는 도중 IOException이 발생할 수 있음을 throws로 명시
    public static void readFileContent(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            // 파일이 존재하지 않으면 IOException을 발생시켜 호출자에게 던짐
            throw new IOException("파일을 찾을 수 없습니다: " + filePath);
        }
        // 실제 파일 읽기 로직 (생략)
        System.out.println(filePath + " 파일을 성공적으로 읽었습니다.");
    }

    public static void main(String[] args) {
        try {
            // readFileContent 메서드 호출 시 예외 발생 가능성이 있으므로 try-catch로 잡음
            readFileContent("myDocument.txt");
            readFileContent("nonExistentFile.txt"); // 여기서 IOException이 발생하여 catch 블록으로 이동
        } catch (IOException e) {
            // 던져진 예외를 main 메서드에서 처리
            System.err.println("메인에서 파일 관련 오류 처리: " + e.getMessage());
        }
        System.out.println("프로그램 계속 실행...");
    }
}
  • 예외를 처리할 수 없거나 처리하는 것이 적절하지 않을 때 던져서 처리한다.
  • 호출자에게 예외 처리 책임이 있거나 복구 로직이 없을 경우 주로 사용한다.

try-catch vs throw

이 둘의 차이는 간단히 말하자면 try는 개발자가 직접 처리하는 것이고 throw는 책임음 떠넘기는 것이다. try는 완전히 복구할 수 있고 프로그램이 안정적으로 지속되어야 할 때 사용한다. 즉, 책임지고 처리하겠다는 거다. 반면 throw는 명확히 처리할 수 없고 책임을 호출자에게 넘길 때 사용한다. 즉, 이건 너의 문제다라고 선언하는 것과 같다.

이 둘을 명확하게 구분해서 사용하기보다는 저수준에서 throw로 던져서 상위 계층이나 ui 계층에서 try-catch로 처리하는 패턴이 주로 나타난다.

태도

  1. 예외 남발: 남발하는 건 코드가 길어지므로 삼가.
  2. catch 구문에 아무것도 안 하는 것은 피해야 함.
  3. 넘길지 아니면 직접 처리할지는 신중하게 결정.
  4. 예외는 구체적으로. 자원은 마지막에 닫히는지 확인.
profile
앱 개발 공부 중

0개의 댓글