본문 바로가기
  • 코딩, 허쌤이 떠먹여 줄게
BackEnd/Java

10장. 예외 처리

by 허쌤 2026. 1. 2.

10장. 예외 처리

예외(Exception) 개념

예외란?

예외(Exception)는 프로그램 실행 중 발생하는 오류나 비정상적인 상황입니다. 예외가 발생하면 프로그램이 중단될 수 있으므로, 적절한 처리가 필요합니다.

예외의 종류

1. 컴파일 오류 (Compile Error)

  • 문법 오류로 인해 컴파일 시점에 발견되는 오류
  • 예: 세미콜론 누락, 괄호 불일치

2. 런타임 오류 (Runtime Error)

  • 프로그램 실행 중 발생하는 오류
  • 예: 배열 인덱스 초과, null 참조

3. 논리 오류 (Logic Error)

  • 프로그램은 실행되지만 의도한 대로 동작하지 않는 오류
  • 예: 잘못된 계산식

예외 클래스 계층 구조

Throwable
├── Error (시스템 오류, 복구 불가능)
└── Exception (예외, 처리 가능)
    ├── RuntimeException (실행 시점 예외)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ArithmeticException
    │   └── ClassCastException
    └── Checked Exception (컴파일 시점 예외)
        ├── IOException
        ├── SQLException
        └── ClassNotFoundException

주요 예외 종류

예외 발생 상황 설명
NullPointerException null 참조 null 객체의 메서드나 필드에 접근
ArrayIndexOutOfBoundsException 배열 인덱스 초과 배열의 범위를 벗어난 인덱스 접근
ArithmeticException 산술 연산 오류 0으로 나누기 등
ClassCastException 타입 변환 오류 잘못된 타입 캐스팅
NumberFormatException 숫자 형식 오류 문자열을 숫자로 변환 실패
IllegalArgumentException 잘못된 인자 메서드에 잘못된 인자 전달

예외 처리의 필요성

// 예외 처리가 없으면 프로그램이 중단됨
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]);  // ArrayIndexOutOfBoundsException 발생!
// 이후 코드는 실행되지 않음

try-catch-finally

try-catch 문

예외를 처리하기 위한 기본 구조입니다.

기본 형식

try {
    // 예외가 발생할 수 있는 코드
} catch (예외타입 변수명) {
    // 예외 처리 코드
}

예제

try {
    int[] numbers = {1, 2, 3};
    System.out.println(numbers[5]);  // 예외 발생 가능
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("배열 인덱스가 범위를 벗어났습니다.");
}

여러 catch 블록

여러 종류의 예외를 각각 처리할 수 있습니다.

try {
    // 예외가 발생할 수 있는 코드
} catch (ArrayIndexOutOfBoundsException e) {
    // 배열 인덱스 예외 처리
} catch (NullPointerException e) {
    // null 참조 예외 처리
} catch (Exception e) {
    // 기타 모든 예외 처리 (가장 마지막에)
}

catch 블록 순서

자식 예외를 부모 예외보다 먼저 처리해야 합니다.

try {
    // 코드
} catch (ArrayIndexOutOfBoundsException e) {
    // 자식 예외 (먼저)
} catch (RuntimeException e) {
    // 부모 예외 (나중에)
} catch (Exception e) {
    // 최상위 예외 (가장 마지막)
}

finally 블록

finally 블록은 예외 발생 여부와 관계없이 항상 실행됩니다.

try {
    // 예외가 발생할 수 있는 코드
} catch (Exception e) {
    // 예외 처리
} finally {
    // 항상 실행되는 코드 (리소스 정리 등)
}

finally 사용 예제

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("0으로 나눌 수 없습니다.");
} finally {
    System.out.println("finally 블록은 항상 실행됩니다.");
}

try-with-resources (Java 7+)

리소스를 자동으로 닫아주는 문법입니다.

// 기존 방식
FileReader fr = null;
try {
    fr = new FileReader("file.txt");
    // 파일 읽기
} catch (IOException e) {
    // 예외 처리
} finally {
    if (fr != null) {
        try {
            fr.close();
        } catch (IOException e) {
            // 예외 처리
        }
    }
}

// try-with-resources (간단)
try (FileReader fr = new FileReader("file.txt")) {
    // 파일 읽기
} catch (IOException e) {
    // 예외 처리
}
// 자동으로 close() 호출됨

예외 발생(throw)

throw 키워드

프로그래머가 직접 예외를 발생시킬 수 있습니다.

기본 형식

throw new 예외타입("예외 메시지");

예제

public int divide(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("0으로 나눌 수 없습니다.");
    }
    return a / b;
}

throws 키워드

메서드에서 발생할 수 있는 예외를 선언합니다.

기본 형식

반환타입 메서드명() throws 예외타입1, 예외타입2 {
    // 메서드 본문
}

예제

public void readFile(String filename) throws IOException {
    FileReader fr = new FileReader(filename);
    // 파일 읽기
    fr.close();
}

// 호출하는 쪽에서 예외 처리
try {
    readFile("data.txt");
} catch (IOException e) {
    System.out.println("파일 읽기 오류: " + e.getMessage());
}

throw vs throws

구분 throw throws
용도 예외를 발생시킴 예외를 선언함
위치 메서드 내부 메서드 선언부
개수 여러 번 사용 가능 여러 예외 선언 가능

사용자 정의 예외

사용자 정의 예외란?

프로그램의 특정 상황에 맞는 예외 클래스를 직접 만드는 것입니다.

사용자 정의 예외 생성

1. Exception을 상속받는 예외 클래스

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

2. RuntimeException을 상속받는 예외 클래스

public class MyRuntimeException extends RuntimeException {
    public MyRuntimeException(String message) {
        super(message);
    }
}

사용자 정의 예외 예제

// 사용자 정의 예외 클래스
public class InsufficientBalanceException extends Exception {
    private double balance;
    private double amount;

    public InsufficientBalanceException(double balance, double amount) {
        super("잔액 부족: 현재 잔액 " + balance + "원, 요청 금액 " + amount + "원");
        this.balance = balance;
        this.amount = amount;
    }

    public double getBalance() {
        return balance;
    }

    public double getAmount() {
        return amount;
    }
}

// 사용 예제
public class BankAccount {
    private double balance;

    public void withdraw(double amount) throws InsufficientBalanceException {
        if (amount > balance) {
            throw new InsufficientBalanceException(balance, amount);
        }
        balance -= amount;
    }
}

Checked Exception vs Unchecked Exception

Checked Exception

  • 컴파일 시점에 반드시 처리해야 하는 예외
  • Exception을 상속 (RuntimeException 제외)
  • try-catch 또는 throws 필수
public class CheckedException extends Exception {
    public CheckedException(String message) {
        super(message);
    }
}

Unchecked Exception

  • 컴파일 시점에 처리하지 않아도 되는 예외
  • RuntimeException을 상속
  • try-catch 또는 throws 선택
public class UncheckedException extends RuntimeException {
    public UncheckedException(String message) {
        super(message);
    }
}

예외 처리 설계 방법

예외 처리 원칙

1. 구체적인 예외 처리

// 나쁜 예: 모든 예외를 하나로 처리
try {
    // 코드
} catch (Exception e) {
    // 모든 예외를 같은 방식으로 처리
}

// 좋은 예: 구체적인 예외 처리
try {
    // 코드
} catch (NullPointerException e) {
    // null 예외 처리
} catch (ArrayIndexOutOfBoundsException e) {
    // 배열 인덱스 예외 처리
} catch (Exception e) {
    // 기타 예외 처리
}

2. 적절한 예외 메시지

// 나쁜 예
throw new Exception("오류");

// 좋은 예
throw new IllegalArgumentException("나이는 0 이상이어야 합니다. 입력값: " + age);

3. 예외를 숨기지 않기

// 나쁜 예: 예외를 무시
try {
    // 코드
} catch (Exception e) {
    // 아무것도 하지 않음
}

// 좋은 예: 예외를 로깅하거나 처리
try {
    // 코드
} catch (Exception e) {
    System.err.println("예외 발생: " + e.getMessage());
    e.printStackTrace();
}

4. 리소스 정리

// finally 블록으로 리소스 정리
FileReader fr = null;
try {
    fr = new FileReader("file.txt");
    // 파일 읽기
} catch (IOException e) {
    // 예외 처리
} finally {
    if (fr != null) {
        try {
            fr.close();
        } catch (IOException e) {
            // 예외 처리
        }
    }
}

예외 처리 패턴

1. 예외 전파

public void method1() throws IOException {
    method2();  // 예외를 호출자에게 전파
}

public void method2() throws IOException {
    // 예외 발생
    throw new IOException("파일 오류");
}

2. 예외 변환

try {
    // 코드
} catch (SQLException e) {
    // 다른 예외로 변환
    throw new DataAccessException("데이터 접근 오류", e);
}

3. 예외 복구

int retryCount = 0;
while (retryCount < 3) {
    try {
        // 작업 수행
        break;  // 성공 시 루프 종료
    } catch (Exception e) {
        retryCount++;
        if (retryCount >= 3) {
            throw e;  // 재시도 실패 시 예외 발생
        }
    }
}

예외 처리 실습 예제

예제 1: 기본 예외 처리

public class ExceptionExample {
    public static void main(String[] args) {
        try {
            int[] numbers = {1, 2, 3};
            System.out.println(numbers[5]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("배열 인덱스 오류: " + e.getMessage());
        }
    }
}

예제 2: 여러 예외 처리

public class MultipleExceptionExample {
    public static void main(String[] args) {
        try {
            String str = null;
            int length = str.length();
            int result = 10 / 0;
        } catch (NullPointerException e) {
            System.out.println("null 참조 오류");
        } catch (ArithmeticException e) {
            System.out.println("산술 연산 오류");
        } catch (Exception e) {
            System.out.println("기타 오류: " + e.getMessage());
        }
    }
}

예제 3: 사용자 정의 예외

public class CustomExceptionExample {
    public static void main(String[] args) {
        try {
            validateAge(-5);
        } catch (InvalidAgeException e) {
            System.out.println("오류: " + e.getMessage());
        }
    }

    public static void validateAge(int age) throws InvalidAgeException {
        if (age < 0 || age > 150) {
            throw new InvalidAgeException("유효하지 않은 나이: " + age);
        }
    }
}

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

연습 문제

  1. 기본 예외 처리

    • 배열 인덱스 예외를 처리하는 프로그램을 작성하세요.
  2. 여러 예외 처리

    • NullPointerException과 ArithmeticException을 각각 처리하는 프로그램을 작성하세요.
  3. 사용자 정의 예외

    • 점수가 0~100 범위를 벗어나면 예외를 발생시키는 프로그램을 작성하세요.
  4. 예외 전파

    • 메서드에서 예외를 발생시키고, 호출하는 쪽에서 처리하는 프로그램을 작성하세요.
  5. finally 블록

    • try-catch-finally를 사용하여 리소스를 정리하는 프로그램을 작성하세요.
  6. 종합 예제

    • 은행 계좌에서 잔액 부족 예외를 처리하는 프로그램을 작성하세요.

다음 장 예고

다음 장에서는 파일 입출력(File I/O)을 통해 데이터를 파일에 저장하고 읽는 방법을 학습합니다.