Java 자바 예외(Exception) 처리와 개념 정리

예외란 무엇인가?

자바에서 예외(Exception)는 프로그램 실행 중에 발생하는 비정상적인 상황을 나타냅니다. 즉, 정상적인 프로그램 흐름에서 벗어나는 예외적인 상황이 발생했을 때, 이를 감지하고 처리하는 것이 바로 예외 처리입니다. 예외는 자바의 중요한 기능 중 하나로, 프로그램이 중단되지 않고 정상적으로 작동할 수 있도록 도와줍니다.

1. 예외의 정의

예외는 프로그램이 실행되는 동안 발생하는 예기치 못한 이벤트 또는 오류 상황입니다. 이러한 예외는 다양한 이유로 발생할 수 있으며, 자바에서는 이를 별도의 객체로 다룹니다. 예외가 발생하면 자바는 해당 예외에 대한 객체를 생성하고, 이를 프로그램의 다른 부분에서 처리할 수 있도록 던집니다(throw). 만약 이 예외를 처리하지 않으면 프로그램은 비정상적으로 종료될 수 있습니다.

예를 들어, 다음과 같은 상황에서 예외가 발생할 수 있습니다.

  • 파일 입출력 오류: 파일을 열거나 읽는 중에 파일이 존재하지 않거나 접근 권한이 없을 때.
  • 네트워크 오류: 인터넷 연결이 끊겼거나 서버와의 통신이 실패할 때.
  • 데이터베이스 오류: SQL 쿼리를 실행하는 도중 데이터베이스와의 연결이 끊기거나 데이터가 잘못되었을 때.
  • 배열 범위 초과: 배열의 유효하지 않은 인덱스에 접근하려 할 때.
  • 널 포인터 참조: 객체가 null인 상태에서 해당 객체의 메서드나 변수를 참조하려 할 때.

2. 예외의 필요성

프로그램을 개발할 때, 모든 상황을 미리 예측하기는 불가능합니다. 예기치 못한 입력, 외부 시스템의 불안정성, 사용자의 잘못된 조작 등이 있을 수 있습니다. 이러한 경우 예외 처리를 통해 프로그램이 비정상적으로 종료되지 않도록 보호할 수 있습니다.

예를 들어, 사용자에게 파일을 입력받아 읽어오는 프로그램을 만든다고 가정해봅시다. 사용자가 입력한 파일이 존재하지 않을 수도 있고, 접근 권한이 없을 수도 있습니다. 이런 상황에서 예외 처리가 없다면, 프로그램은 해당 오류를 만나면서 비정상적으로 종료될 것입니다. 그러나 예외 처리를 통해 프로그램이 이러한 상황을 인식하고 적절히 대응하면, 사용자에게 오류 메시지를 보여주고 프로그램을 안전하게 종료하거나 다시 입력을 받을 수 있습니다.

3. 예외와 오류(Error)의 차이

자바에서 예외는 Throwable 클래스를 상속받은 두 가지 주요 클래스, 즉 Exception과 Error로 나뉩니다. 이 두 가지는 발생하는 상황이 다릅니다.

  • Exception (예외): 일반적인 프로그램 실행 중에 발생하는 비정상적인 상황입니다. 이는 개발자가 예측할 수 있고, 처리할 수 있습니다. 예를 들어, 파일 입출력 중 발생할 수 있는 IOException이 있습니다.
  • Error (오류): 시스템 레벨에서 발생하는 심각한 문제로, 프로그램에서 복구할 수 없는 상황입니다. 주로 JVM(Java Virtual Machine) 내부에서 발생하며, 메모리가 부족할 때 발생하는 OutOfMemoryError나 스택 오버플로우가 발생할 때의 StackOverflowError가 있습니다. 이러한 오류는 개발자가 처리하기보다 시스템 자체의 문제로 보는 것이 일반적입니다.

따라서 Exception은 프로그램에서 처리 가능한 비정상적인 상황을 의미하고, Error는 복구할 수 없는 심각한 시스템 레벨의 문제를 나타냅니다.

4. Checked 예외와 Unchecked 예외

자바에서는 예외를 크게 두 가지로 분류합니다. Checked 예외Unchecked 예외입니다.

Java 자바 예외(Exception) 처리와 개념 정리
Checked 예외
  • 컴파일 시점에서 발생할 가능성이 있는 예외로, 반드시 처리해야 하는 예외입니다.
  • Exception 클래스를 상속받는 예외들이 주로 여기에 해당합니다.
  • 이러한 예외는 자바 컴파일러에 의해 검사되며, 개발자가 예외 처리를 하지 않으면 컴파일 오류가 발생합니다.
  • 예를 들어, 파일 입출력과 관련된 IOException, 데이터베이스 연결 관련 예외인 SQLException 등이 여기에 해당합니다.
Java
public void readFile(String filePath) throws IOException {
    FileReader file = new FileReader(filePath);
    file.read();
}
Java

위의 코드에서 FileReader는 파일을 읽을 때 발생할 수 있는 예외를 체크 예외로 처리합니다. 따라서 개발자는 이를 반드시 처리해야 합니다.

Unchecked 예외
  • 런타임에 발생하는 예외로, 컴파일러가 예외 처리를 강제하지 않는 예외입니다.
  • RuntimeException 클래스를 상속받는 예외들이 주로 여기에 해당합니다.
  • 일반적으로 프로그래밍 오류나 잘못된 논리로 인해 발생하며, 예외 처리를 강제하지 않기 때문에 발생 시점에 처리하지 않으면 프로그램이 비정상적으로 종료될 수 있습니다.
  • 대표적인 예로 NullPointerException, ArrayIndexOutOfBoundsException 등이 있습니다.
Java
public void divide(int a, int b) {
    int result = a / b; // b가 0일 경우 ArithmeticException 발생
}
Java

5. 예외 발생 흐름

자바에서 예외가 발생하면, 다음과 같은 단계로 흐름이 진행됩니다.

  1. 예외 발생 (throwing)
    • 예외가 발생하면, JVM은 예외 객체를 생성하고, 이를 던집니다(throw).
    • 던져진 예외는 적절한 catch 블록이 있는지 확인합니다.
  2. 예외 처리 (catching)
    • 발생한 예외가 처리될 때까지 호출 스택을 거슬러 올라가며 catch 블록을 찾습니다.
    • 예외를 처리할 수 있는 catch 블록이 발견되면 그 안에 있는 코드가 실행됩니다.
  3. 예외 전파 (propagation)
    • 만약 catch 블록이 발견되지 않으면, 예외는 계속해서 호출 스택을 타고 올라가며 전파됩니다. 최종적으로 main 메서드까지 전파되며, 처리되지 않은 예외는 프로그램을 종료시킵니다.

6. 예외 처리 방법

자바에서는 주로 try-catch-finally 블록을 사용하여 예외를 처리합니다. 각각의 키워드가 어떻게 사용되는지 알아보겠습니다.

  • try 블록: 예외가 발생할 가능성이 있는 코드를 포함합니다. 예외가 발생하면 해당 코드의 실행이 중단되고 catch 블록으로 제어가 넘어갑니다.
  • catch 블록: 특정 예외가 발생했을 때 이를 처리하는 코드를 포함합니다. 발생한 예외의 종류에 따라 여러 개의 catch 블록을 사용할 수 있습니다.
  • finally 블록: 예외 발생 여부와 관계없이 항상 실행되는 블록으로, 주로 리소스 해제와 같은 작업을 수행합니다. 예를 들어 파일이나 데이터베이스 연결을 닫는 작업이 포함될 수 있습니다.
Java
public void processFile(String fileName) {
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader(fileName));
        System.out.println(reader.readLine());
    } catch (FileNotFoundException e) {
        System.err.println("파일을 찾을 수 없습니다: " + e.getMessage());
    } catch (IOException e) {
        System.err.println("입출력 오류가 발생했습니다: " + e.getMessage());
    } finally {
        try {
            if (reader != null) {
                reader.close();
            }
        } catch (IOException e) {
            System.err.println("리소스 해제 중 오류 발생: " + e.getMessage());
        }
    }
}
Java

위 예제에서 try 블록은 파일을 읽는 코드를 포함하고, catch 블록은 예외 발생 시 에러 메시지를 출력하며, finally 블록은 예외와 관계없이 파일을 닫는 작업을 수행합니다.

7. 사용자 정의 예외

자바에서는 기본적으로 제공되는 예외 클래스 외에도, 사용자가 직접 예외 클래스를 정의하여 사용할 수 있습니다. 사용자 정의 예외는 특정한 비즈니스 로직에서 발생할 수 있는 오류를 보다 명확하게 나타내기 위해 사용됩니다.

Java
class InvalidUserException extends Exception {
    public InvalidUserException(String message) {
        super(message);
    }
}

public void validateUser(String user) throws InvalidUserException {
    if (user == null || user.isEmpty()) {
        throw new InvalidUserException("유효하지 않은 사용자입니다.");
    }
}
Java

위 예제에서는 InvalidUserException이라는 사용자 정의 예외를 만들어, 사용자 유효성 검증 과정에서 조건을 만족하지 않는 경우 명확하게 예외를 발생시킵니다.

8. 예외 전파(Exception Propagation)

예외 전파란 메서드에서 예외가 발생했을 때, 해당 메서드에서 이를 처리하지 않고 호출한 메서드로 예외를 전달하는 것을 의미합니다. 예외 전파는 throws 키워드를 통해 처리되며, 예외가 전파되는 과정에서 호출 스택이 출력되어 문제의 원인을 추적하는 데 유용합니다.

Java
public void methodA() throws IOException {
    methodB();
}

public void methodB() throws IOException {
    throw new IOException("IO 오류 발생");
}
Java

위 코드에서 methodB에서 발생한 예외는 methodA로 전파되고, 최종적으로 methodA를 호출한 쪽에서 예외 처리를 수행하게 됩니다.

9. try-with-resources

자바 7에서 도입된 try-with-resources는 리소스를 자동으로 해제할 수 있는 기능을 제공합니다. AutoCloseable 인터페이스를 구현한 객체는 try-with-resources 블록을 사용해 자동으로 닫을 수 있습니다.

Java
public void readFile(String fileName) {
    try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
        System.out.println(reader.readLine());
    } catch (IOException e) {
        System.err.println("파일 읽기 중 오류 발생: " + e.getMessage());
    }
}
Java

위 코드에서는 try 블록에서 BufferedReader를 사용하고, 별도의 finally 블록 없이도 리소스가 자동으로 해제됩니다.

10. 예외 처리의 모범 사례

  • 특정 예외 처리: 가능하면 Exception을 catch하기보다 구체적인 예외를 처리하는 것이 좋습니다. 이를 통해 문제의 원인을 정확히 파악하고 해결할 수 있습니다.
  • 적절한 로깅: 예외가 발생했을 때 적절히 로그를 남기는 것이 중요합니다. 예외의 스택 트레이스를 로그에 기록하여 문제 해결 시 유용하게 사용할 수 있습니다.
  • 사용자에게 의미 있는 메시지 제공: 예외 상황을 사용자에게 알릴 때는 가능한 한 이해하기 쉬운 메시지를 제공해야 합니다. 내부 예외 메시지를 그대로 노출하는 것은 보안상의 위험을 초래할 수 있으므로 주의가 필요합니다.

결론

자바의 예외 처리 메커니즘은 프로그램의 안정성을 높이는 중요한 도구입니다. 예외의 종류와 발생하는 상황에 대해 이해하고, 이를 올바르게 처리하는 방법을 익히면 보다 견고한 프로그램을 작성할 수 있습니다. 예외 처리는 단순히 프로그램의 오류를 막는 것을 넘어, 사용자 경험을 향상시키고, 시스템의 신뢰성을 높이는 데 중요한 역할을 합니다. 앞으로 코드를 작성할 때 예외 처리를 염두에 두고, 발생할 수 있는 오류에 대해 적절히 대응하는 습관을 기르길 바랍니다.

Leave a Comment