CodeGym /행동 /JAVA 25 SELF /API의 일부로서의 예외와 try-with-resources

API의 일부로서의 예외와 try-with-resources

JAVA 25 SELF
레벨 24, 레슨 4
사용 가능

1. API의 일부로서의 예외

왜 예외는 메서드의 “계약”의 일부일까요?

메서드를 작성할 때는 매개변수와 반환값뿐 아니라 그 메서드가 어떤 예외를 던질 수 있는지도 정의합니다. 이는 당신의 메서드와 그것을 사용할 사람들 사이의 “계약”의 일부입니다. 메서드가 예외를 던질 수 있다면, 호출하는 쪽은 이를 알아야 올바르게 오류를 처리할 수 있습니다(혹은 최소한 프로그램이 갑자기 오류로 종료될 때 놀라지 않도록).

예시:

public void readFile(String filename) throws IOException {
    // ... 파일 읽기
}

여기서는 메서드가 IOException을 던질 수 있음을 명시합니다. 이는 메서드 사용자에게 “파일 읽기 오류를 처리할 준비를 하라!”라는 신호입니다.

예외 문서화: Javadoc의 @throws 태그

다른 개발자가 메서드가 어떤 예외를 던질 수 있는지 이해하도록 Javadoc에서 @throws(또는 @exception) 태그를 사용하세요.

예시:

/**
 * 파일 내용을 읽습니다.
 *
 * @param filename 파일 이름
 * @return 파일 내용을 문자열로 반환
 * @throws IOException 파일 읽기 중 오류가 발생한 경우
 */
public String readFile(String filename) throws IOException {
    // ...
}

왜 필요할까요?

  • 다른 개발자가 어떤 오류를 처리해야 하는지 이해하는 데 도움이 됩니다.
  • IDE와 문서 생성기(예: Javadoc)가 이러한 예외를 힌트로 바로 보여줍니다.
  • 코드의 신뢰성과 예측 가능성을 높입니다.

API에서 checked/unchecked 예외

Checked 예외(Exception의 하위 클래스이지만 RuntimeException은 아님)는 메서드 계약의 일부입니다. 반드시 처리하거나 (try-catch) 명시적으로 더 바깥으로 던져야 합니다(throws).

Unchecked 예외(RuntimeException 및 그 하위 클래스)는 보통 프로그래밍 오류를 나타냅니다(예: NullPointerException, IllegalArgumentException). 시그니처에 반드시 명시할 필요는 없지만, 메서드가 이런 오류를 던질 수 있다면(예: 잘못된 인수) Javadoc에 이를 기술하는 것이 좋습니다.

예시:

/**
 * a를 b로 나눕니다.
 * @param a 피제수
 * @param b 제수
 * @return 나눗셈 결과
 * @throws IllegalArgumentException b == 0인 경우
 */
public int divide(int a, int b) {
    if (b == 0) throw new IllegalArgumentException("분모는 0이 될 수 없습니다");
    return a / b;
}

예외와 API 설계

  • 메서드의 사용자 관점에서 생각하세요: 어떤 오류는 사용자가 처리할 수 있고, 어떤 것은 버그인지, 어떤 것은 “정상적인” 상황인지 구분하세요.
  • Checked 예외를 남용하지 마세요: 오류가 버그(예: 잘못된 인수)라면 unchecked 예외를 던지는 편이 낫습니다.
  • 위로 전파될 수 있는 모든 예외를 문서화하세요.

2. try-with-resources 구문

문제: 자원을 안전하게 닫으려면?

많은 작업에서 사용 후 반드시 닫아야 하는 자원(파일, 네트워크 연결, 데이터베이스 등)을 다룹니다. 자원을 닫는 것을 잊으면 메모리 누수, 파일 잠금, 기타 문제들이 발생할 수 있습니다.

예전 방식:

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("data.txt"));
    String line = reader.readLine();
    // ...
} catch (IOException e) {
    // 오류 처리
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            // 닫는 중 오류 처리
        }
    }
}

코드가 많고, 실수하기 쉽고, 자원을 닫는 것을 잊기 쉽습니다.

해결책: try-with-resources

Java 7부터 try-with-resources 구문이 도입되어, 블록 내부에서 예외가 발생해도 모든 자원을 자동으로 닫아 줍니다.

문법:

try (ResourceType resource = new ResourceType(...)) {
    // 자원 사용
} catch (ExceptionType e) {
    // 오류 처리
}

예시:

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("파일 읽기 중 오류: " + e.getMessage());
}
// reader.close()는 자동으로 호출됩니다!

동작 원리

  • try 뒤의 괄호 안에 닫아야 하는 자원을 선언합니다.
  • try 블록을 벗어나면(예외가 발생해도!) 각 자원에 대해 close()가 호출됩니다.
  • 이는 AutoCloseable(또는 그 상위 Closeable)을 구현한 자원에만 동작합니다.

AutoCloseable 인터페이스:

public interface AutoCloseable {
    void close() throws Exception;
}

모든 표준 Java 자원(파일, 스트림, DB 연결 등)은 이 인터페이스를 구현합니다.

여러 자원을 선언할 수 있습니다

try (
    BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
    BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line);
        writer.newLine();
    }
}

예외가 발생하더라도 두 자원 모두 자동으로 닫힙니다.

try-with-resources의 장점

  • 안전성: 오류가 발생해도 자원이 항상 닫힙니다.
  • 간결성: 코드가 줄어들어 실수할 여지가 적습니다.
  • 가독성: 어떤 자원이 사용되고 언제 닫히는지 바로 알 수 있습니다.

3. 실습: try-with-resources로 안전한 코드 작성

예시: 파일 읽기

public static void printFirstLine(String filename) {
    try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
        String line = reader.readLine();
        System.out.println("첫 줄: " + line);
    } catch (IOException e) {
        System.out.println("오류: " + e.getMessage());
    }
}

예시: 파일 쓰기

public static void writeToFile(String filename, String text) {
    try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
        writer.write(text);
    } catch (IOException e) {
        System.out.println("쓰기 중 오류: " + e.getMessage());
    }
}

예시: 사용자 정의 자원

직접 닫아야 하는 클래스를 작성한다면 AutoCloseable을 구현하세요:

public class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("자원이 닫혔습니다!");
    }
}

이제 이를 try-with-resources에서 사용할 수 있습니다:

try (MyResource res = new MyResource()) {
    // 자원 사용
} 

4. 흔한 실수와 모범 사례

오류 1: 자원을 닫는 것을 잊음(try-with-resources 없이).
try-with-resources를 사용하지 않으면 파일이나 스트림을 닫는 것을 쉽게 잊게 되어 자원 누수로 이어질 수 있습니다.

오류 2: AutoCloseable을 구현하지 않은 객체에 try-with-resources를 사용.
해당 인터페이스를 구현하지 않으면 컴파일러가 try-with-resources에서 사용할 수 없도록 막습니다.

오류 3: API에서 예외를 문서화하지 않음.
메서드가 예외를 던질 수 있다면 시그니처(throws)와 Javadoc(@throws)에 반드시 명시하세요. 이는 다른 사람이 코드를 올바르게 사용하도록 도와줍니다.

오류 4: 구체적인 예외 대신 Exception을 포착.
실제로 예상하고 처리할 수 있는 예외만 잡는 것이 좋습니다.

1
과제
JAVA 25 SELF, 레벨 24, 레슨 4
잠금
뉴스 애그리게이터: 기사 제목 자동 읽기
뉴스 애그리게이터: 기사 제목 자동 읽기
1
과제
JAVA 25 SELF, 레벨 24, 레슨 4
잠금
마법 유물: 자동 종료
마법 유물: 자동 종료
1
과제
JAVA 25 SELF, 레벨 24, 레슨 4
잠금
과학 연구실: 데이터의 잠재적 오류 문서화
과학 연구실: 데이터의 잠재적 오류 문서화
1
과제
JAVA 25 SELF, 레벨 24, 레슨 4
잠금
디지털 기록 관리자: 고대 원고의 안전한 복사
디지털 기록 관리자: 고대 원고의 안전한 복사
1
설문조사/퀴즈
예외 계층 구조, 레벨 24, 레슨 4
사용 불가능
예외 계층 구조
예외 고급 다루기
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION