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을 포착.
실제로 예상하고 처리할 수 있는 예외만 잡는 것이 좋습니다.
GO TO FULL VERSION