[Java] 예외 처리(Exception Handling)

션션·2026년 1월 28일

Java

목록 보기
9/10
post-thumbnail

1. 예외와 에러, 무엇이 다를까?

자바에서는 실행 시 발생할 수 있는 오류를 크게 두 가지로 분류합니다.

  • 에러(Error): 메모리 부족()이나 스택 오버플로우()처럼 발생하면 복구할 수 없는 심각한 상황입니다.
  • 예외(Exception): 코드 작성을 통해 비정상 종료를 막고 수습할 수 있는 비교적 가벼운 상황입니다.

예외의 계층 구조

자바의 모든 예외와 에러는 Throwable 클래스를 상속받습니다. 여기서 예외(Exception)는 다시 두 갈래로 나뉩니다.

구분Checked ExceptionUnchecked Exception (Runtime)
대표 클래스IOException, SQLExceptionRuntimeException 하위 클래스들
체크 시점컴파일 단계에서 확인실행 시점(Runtime)에 발생
처리 강제반드시 try-catchthrows 필요명시적인 처리를 강제하지 않음
의미외부 환경(파일, 네트워크)에 의한 오류개발자의 실수(0으로 나누기, 인덱스 초과)

💡 비유로 이해하기

  • Unchecked: 길을 걷다 돌부리에 걸리는 것. 조심하면 예방할 수 있기에 미리 구급차를 대기시키지 않습니다.
  • Checked: 태풍이 오는 날 외출하는 것. 아무리 조심해도 사고가 날 수 있으니, 국가(컴파일러)가 우비나 장화(try-catch)를 챙기지 않으면 밖으로 나가지 못하게 막는 것과 같습니다.

2. 예외 처리의 핵심 키워드 (try-catch-finally)

기본 흐름

try {
    // (1) 예외 발생 가능성이 있는 코드
    // (2) 정상 실행 시 다음 코드
} catch (ArithmeticException e) {
    // (3) 특정 예외 발생 시 처리할 코드
} finally {
    // (4) 예외 발생 여부와 상관없이 '무조건' 실행
}
  • 실행 순서: * 예외 발생 시: (1) → (3) → (4)
  • 정상 작동 시: (1) → (2) → (4)
  • 주의: finally 블록은 trycatch 안에 return 문이 있더라도 반드시 실행된 후 메서드가 종료됩니다.

다중 catch와 순서의 비밀

하나의 try 블록에서 여러 종류의 예외가 발생할 수 있습니다. 이때 catch 문을 작성하는 중요한 규칙이 있습니다.

"좁은 범위(자식)에서 넓은 범위(조상) 순으로 작성하라!"

예외 객체가 발생하면 위에서부터 차례대로 instanceof로 체크하며 내려옵니다. 만약 가장 상위 클래스인 Exception을 첫 번째 catch에 두면, 아래에 있는 상세한 예외 블록들은 절대 실행될 기회를 얻지 못합니다.


3. try-with-resources

파일이나 네트워크 연결 같은 자원(Resource)은 사용 후 반드시 close()를 호출해야 합니다. 예전에는 finally 블록 안에서 또 try-catch를 써가며 지저분하게 코드를 짰지만, JDK 7부터는 아주 깔끔해졌습니다.

// AutoCloseable 인터페이스를 구현한 객체만 가능
try (FileInputStream fis = new FileInputStream("data.txt")) {
    // 로직 수행
} catch (IOException e) {
    e.printStackTrace();
} // 블록을 벗어나는 순간 fis.close()가 자동 호출됨

4. 예외 던지기 (throws vs throw)

  • throw: 개발자가 직접 예외를 발생시킬 때 사용합니다. (예: throw new MyException("에러 발생");)
  • throws: 메서드 선언부에 사용하며, "이 메서드에서 이런 예외가 터질 수 있으니 나를 호출하는 쪽에서 처리해!"라고 위임하는 것입니다.

5. 메서드 재정의(Overriding) 시 주의사항

부모 클래스의 메서드를 자식 클래스에서 재정의할 때, 던질 수 있는 예외에도 규칙이 있습니다.

  1. 부모 메서드보다 더 조상 타입의 예외를 던질 수 없습니다.
  2. 부모 메서드보다 더 구체적인(하위) 예외는 던질 수 있습니다.
  3. RuntimeException 계열은 부모 메서드에 상관없이 자유롭게 던질 수 있습니다.
class Parent {
    void service() throws IOException {}
}

class Child extends Parent {
    @Override
    // void service() throws Exception { } // (X) 부모보다 큰 예외 불가
    void service() throws FileNotFoundException { } // (O) 더 구체적인 예외 가능
}

6. 사용자 정의 예외 만들기

도메인 특화된 에러를 표현하고 싶을 때 직접 예외 클래스를 만듭니다.

// 1. 클래스 생성 (Exception 혹은 RuntimeException 상속)
public class ItemNotFoundException extends RuntimeException {
    public ItemNotFoundException(String itemName) {
        super(itemName + "을(를) 찾을 수 없습니다.");
    }
}

// 2. 활용 예제
public void findItem(String name) {
    if (!store.contains(name)) {
        throw new ItemNotFoundException(name); // 명시적 발생
    }
}

💡 마무리 퀴즈 (복습 체크)

  • Q: try-with-resources를 사용하기 위해 구현해야 하는 인터페이스는?
  • A: AutoCloseable
  • Q: Checked Exception은 어떤 상황에서 주로 쓰이나요?
  • A: 파일 입출력, 네트워크 연결 등 개발자가 주의해도 외부 요인에 의해 발생할 수 있는 상황에서 예외 처리를 강제하기 위해 사용합니다.

0개의 댓글