게으른 개발자의 끄적거림

Java 예외(Exception) 처리 방법 (feat. throws, throw)

끄적잉 2024. 10. 28. 21:42

Java 예외(Exception) 처리 방법 (feat. throws, throw)

Java에서 예외(Exception)를 발생시키는 방법에 대해 알아보겠습니다. 예외 처리는 Java 프로그램이 실행 중 예상치 못한 오류가 발생했을 때 프로그램이 비정상적으로 종료되지 않고, 이를 처리할 수 있는 방법을 제공합니다. 예외는 주로 파일 접근 오류, 네트워크 오류, 숫자 계산 오류, 타입 변환 오류 등 다양한 상황에서 발생할 수 있으며, 이러한 상황을 개발자가 사전에 인지하고 예외 처리를 통해 프로그램의 안정성을 높일 수 있습니다. 

 

### 1. 예외의 개념과 종류

Java의 예외는 `Throwable` 클래스를 기반으로 한 두 가지 큰 종류로 나뉩니다:

1. **Checked Exception (체크 예외)**  
   - 컴파일 타임에 반드시 예외 처리를 해야 하는 예외입니다. 컴파일러가 `try-catch` 블록 또는 `throws` 선언을 요구합니다.
   - 대표적으로 `IOException`, `SQLException`, `ClassNotFoundException` 등이 있습니다.

2. **Unchecked Exception (언체크 예외)**  
   - 실행 시점에 발생하는 예외로, `RuntimeException` 클래스를 상속한 예외들입니다. 이 예외는 컴파일러가 예외 처리를 강제하지 않습니다.
   - 대표적으로 `NullPointerException`, `ArrayIndexOutOfBoundsException`, `ArithmeticException` 등이 있습니다.

 

### 2. 예외 발생 시키기 (`throw` 키워드 사용법)

Java에서는 `throw` 키워드를 사용해 예외를 강제로 발생시킬 수 있습니다. `throw`는 특정 조건이 만족될 때 예외를 명시적으로 던질 때 사용되며, 발생시키려는 예외 객체를 지정해야 합니다.

```java
throw new Exception("예외 메시지");
```

예를 들어, 특정 값이 0일 때 `ArithmeticException`을 발생시키는 코드는 다음과 같습니다.

```java
public void divide(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("0으로 나눌 수 없습니다.");
    }
    System.out.println("결과: " + (a / b));
}
```

위의 코드에서 `b`가 0일 경우 `ArithmeticException` 예외가 발생합니다. 이와 같이 `throw` 키워드를 통해 실행 중 특정 조건에서 예외를 발생시킬 수 있습니다.

### 3. 사용자 정의 예외 클래스

Java에서는 `Exception` 클래스를 상속하여 사용자 정의 예외를 만들 수 있습니다. 사용자 정의 예외를 만들면 프로그램 로직에 맞춘 세부적인 예외 처리가 가능해집니다. 예를 들어, 은행 애플리케이션에서 출금할 금액이 잔고보다 많을 경우 발생시키는 `InsufficientFundsException` 예외를 작성해 보겠습니다.

#### 사용자 정의 예외 클래스 생성하기

```java
public class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
}
```

#### 사용자 정의 예외 발생시키기

```java
public class BankAccount {
    private double balance;

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

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("잔고 부족: 현재 잔고는 " + balance + "입니다.");
        }
        balance -= amount;
        System.out.println("출금 완료: 남은 잔고는 " + balance + "입니다.");
    }
}
```

위의 예제에서는 `withdraw` 메소드가 `InsufficientFundsException` 예외를 발생시킵니다. 사용자는 잔고가 부족할 경우에만 예외가 발생하므로, 프로그램 내에서 이를 처리할 수 있습니다.

### 4. `try-catch`로 예외 처리하기

`throw` 키워드로 예외를 발생시키면, 예외를 처리하지 않는 한 프로그램은 비정상 종료됩니다. 이를 방지하려면 `try-catch` 구문을 사용하여 예외를 잡아내고 처리할 수 있습니다. 예를 들어, 앞서 작성한 `BankAccount` 클래스를 사용하는 코드는 다음과 같이 작성할 수 있습니다.

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

        try {
            account.withdraw(1500);
        } catch (InsufficientFundsException e) {
            System.out.println("예외 발생: " + e.getMessage());
        }
    }
}
```

위 코드에서는 `InsufficientFundsException`이 발생했을 때 `catch` 블록에서 예외 메시지를 출력합니다. 이로써 프로그램이 비정상 종료되지 않고, 예외 상황을 처리하여 계속 진행될 수 있습니다.

### 5. `throws` 키워드를 통한 예외 전달

메소드 내부에서 예외를 발생시키는 경우, 호출된 메소드로 예외를 전달하고 싶다면 `throws` 키워드를 사용할 수 있습니다. `throws`는 메소드 시그니처에 선언되며, 예외를 발생시킨 메소드를 호출한 메소드가 예외를 처리하게 합니다.

#### `throws` 예제 코드

```java
public void readFile(String filePath) throws IOException {
    FileReader file = new FileReader(filePath);
    BufferedReader fileInput = new BufferedReader(file);

    // 파일에서 첫 줄을 읽어오기
    System.out.println(fileInput.readLine());
    fileInput.close();
}
```

위 메소드에서는 파일을 읽을 때 `IOException`이 발생할 수 있습니다. 이 경우 `readFile` 메소드는 `IOException`을 직접 처리하지 않고 호출한 메소드로 전달합니다.

```java
public static void main(String[] args) {
    try {
        readFile("test.txt");
    } catch (IOException e) {
        System.out.println("파일 읽기 오류: " + e.getMessage());
    }
}
```

이와 같이 `throws` 키워드를 사용해 예외를 호출한 쪽으로 전달하면, 코드가 더 간결해지고 예외 처리의 책임을 호출자에게 넘길 수 있습니다.

### 6. `finally` 블록을 통한 리소스 해제

Java에서는 `try-catch-finally` 구문을 통해 예외 발생 여부에 상관없이 항상 실행되는 `finally` 블록을 사용할 수 있습니다. `finally` 블록은 주로 열려 있는 리소스(파일, 네트워크 연결 등)를 해제할 때 사용됩니다.

```java
public void readFile(String filePath) {
    BufferedReader fileInput = null;

    try {
        FileReader file = new FileReader(filePath);
        fileInput = new BufferedReader(file);
        System.out.println(fileInput.readLine());
    } catch (IOException e) {
        System.out.println("파일 읽기 오류: " + e.getMessage());
    } finally {
        if (fileInput != null) {
            try {
                fileInput.close();
            } catch (IOException e) {
                System.out.println("파일 닫기 오류: " + e.getMessage());
            }
        }
    }
}
```

위 코드에서는 파일을 읽는 동안 예외가 발생하더라도 `finally` 블록이 실행되어 파일 리소스가 닫히도록 보장합니다.

### 7. `try-with-resources` 구문

Java 7부터는 `try-with-resources` 구문이 도입되어, `AutoCloseable` 인터페이스를 구현한 객체에 대해 자동으로 리소스를 해제할 수 있습니다. 이를 통해 `finally` 블록 없이도 리소스를 안전하게 닫을 수 있습니다.

```java
public void readFile(String filePath) {
    try (BufferedReader fileInput = new BufferedReader(new FileReader(filePath))) {
        System.out.println(fileInput.readLine());
    } catch (IOException e) {
        System.out.println("파일 읽기 오류: " + e.getMessage());
    }
}
```

`try-with-resources` 구문을 사용하면 `fileInput` 객체가 `try` 블록이 끝나는 시점에 자동으로 닫히므로 코드가 더 깔끔하고 안전합니다.