개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Java › 예외

체크 예외와 언체크 예외

어떤 예외는 왜 처리하지 않으면 컴파일조차 안 되나

자바의 예외는 두 갈래입니다. 체크 예외(IOException 등)는 잡거나(try-catch) 던진다고 선언(throws)하지 않으면 컴파일러가 컴파일을 거부합니다. 언체크 예외(대표적으로 RuntimeException 계열)는 그런 강제가 없어서 아무 처리 없이도 컴파일됩니다.

실행으로 대조 · JDK 21에서 스물네 가지 조합을 실제로 컴파일하고 실행해 대조

이 실습에 나오는 개념

Checked 예외와 Unchecked 예외
Unchecked 예외는 RuntimeException과 그 하위 클래스로(명세상으로는 Error 계열도 unchecked에 속한다), 컴파일러가 처리 여부를 검사하지 않는다(NullPointerException, IllegalArgumentException 등). Checked 예외는 그 밖의 Exception으로, 반드시 catch로 처리하거나 throws로 선언해야 컴파일된다(IOException, SQLException 등). 스프링의 @Transactional은 기본적으로 RuntimeException과 Error에서만 롤백하고 Checked 예외에서는 커밋한다.
예외(Exception)와 Error
둘 다 프로그램 실행 중의 비정상 상황을 나타내는 Throwable의 하위 타입이다. Exception은 애플리케이션이 잡아서 처리할 수 있는 상황(잘못된 입력, 파일 없음 등)이고, Error는 메모리 부족이나 스택 오버플로처럼 JVM 수준의 심각한 문제라 애플리케이션이 복구를 시도하지 않는 것이 일반적이다.
throws 선언
메서드 선언 끝에 throws IOException처럼 적어, 이 메서드가 그 예외를 밖으로 던질 수 있음을 알리는 문법이다. 체크 예외를 메서드 안에서 잡지 않으려면 반드시 적어야 하고, 그러면 그 메서드를 호출하는 쪽이 다시 잡거나 throws로 선언해야 컴파일된다. 예외를 처리하는 것이 아니라 처리할 의무를 호출한 쪽으로 넘기는 것이다. 언체크 예외는 적지 않아도 되며, 적더라도 호출하는 쪽에 의무가 생기지 않는다. 예외를 실제로 던지는 문장인 throw와는 다른 낱말이다.
예외 감싸기
잡은 예외를 다른 종류의 예외 안에 넣어 다시 던지는 것이다. 체크 예외를 catch로 잡아 throw new RuntimeException(e)처럼 언체크 예외로 바꿔 던지면, 지나가는 메서드마다 throws를 적지 않아도 컴파일된다. 원래 예외를 생성자에 넘기면 원인(cause)으로 보관되어, 오류 출력의 Caused by 아래에 함께 나온다. 원래 예외를 넘기지 않으면 처음에 무엇이 잘못됐는지 알 수 없게 되므로 반드시 넘긴다. 감싸도 예외가 사라지는 것은 아니어서, 어디에서도 잡지 않으면 실행이 중단된다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. 컴파일러가 read 메서드를 검사합니다: 안에 throw new IOException(...)이 있습니다. IOException은 체크 예외입니다. 체크 예외가 나올 수 있는 줄은 try-catch로 감싸거나, 그 메서드의 선언에 throws를 적어야만 컴파일됩니다.
  2. read에는 try-catch도 throws 선언도 없습니다: 컴파일 오류입니다. 컴파일러는 "unreported exception IOException; must be caught or declared to be thrown"라고 알립니다. "알리지 않은 예외 IOException이 있다. 잡거나, 던진다고 선언해야 한다"는 뜻입니다.
  3. 컴파일이 실패했습니다: class 파일이 만들어지지 않으므로 프로그램을 실행할 수 없습니다. 실행 중에 생길 문제를 실행 전에 미리 막은 것입니다.

실행 결과

코드

import java.io.IOException;

public class Main {
    static String read(boolean missing) {
        if (missing) throw new IOException("파일 없음");
        return "내용";
    }

    public static void main(String[] args) {
        String s = read(true);
        System.out.println("읽음: " + s);
        System.out.println("main 끝");
    }
}

이 모형의 전제

스물네 가지 조합 모두를 이 PC의 JDK 21에서 화면의 코드와 같은 소스로 실제로 컴파일하고, 컴파일된 것은 실행해서 컴파일 여부, 오류가 난 줄, 컴파일러 메시지, 출력, 종료 코드, 오류 출력의 첫 줄을 대조했습니다. 컴파일러 메시지는 명령줄 javac가 보여 주는 모양(클래스 이름만)으로 적었습니다. Error 계열을 아무 처리 없이 던져도 컴파일된다는 것과, Exception 자체를 던지면 체크 예외로 취급된다는 것도 같은 방법으로 확인했습니다. 모형은 메서드가 둘(read, main)인 경우만 다룹니다. 호출이 더 깊어도 규칙은 같아서, 체크 예외가 지나가는 메서드마다 잡거나 선언해야 합니다. 잡히지 않은 예외가 프로그램을 끝내는 것은 스레드가 main 하나뿐인 경우의 이야기입니다.

앞 실습: 예외와 finally · 다음 실습: 제네릭

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요