메서드가 던지는 예외 지정하기
이전 섹션에서는 ListOfNumbers 클래스의 writeList() 메서드에 대한 예외 핸들러를 작성하는 방법을 살펴봤습니다. 때로는 코드 내에서 발생할 수 있는 예외를 코드가 직접 잡는 것이 적절할 수 있습니다. 그러나 다른 경우에는 호출 스택의 더 위쪽에 있는 메서드가 예외를 처리하도록 하는 것이 더 좋습니다. 예를 들어, 클래스 패키지의 일부로 ListOfNumbers 클래스를 제공하는 경우 패키지의 모든 사용자의 요구를 예상할 수 없을 수 있습니다. 이 경우 예외를 catch하지 않고 호출 스택의 상위 메서드가 예외를 처리하도록 허용하는 것이 좋습니다.
writeList() 메서드가 그 안에서 발생할 수 있는 checked exceptions을 catch하지 않는다면, writeList() 메서드는 이러한 예외를 던질 수 있도록 지정해야 합니다. 예외를 catch하는 대신 던질 수 있는 예외를 지정하도록 원래의 writeList() 메서드를 수정해 보겠습니다. 참고로 컴파일되지 않는 writeList() 메서드의 원래 버전은 다음과 같습니다.
public void writeList() {
PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
out.println("Value at: " + i + " = " + list.get(i));
}
out.close();
}writeList()가 두 가지 예외를 던질 수 있도록 지정하려면 writeList() 메서드 선언에 throws 절을 추가합니다. throws 절은 throws 키워드와 그 뒤에 쉼표로 구분된 해당 메서드가 던지는 모든 예외의 목록으로 구성됩니다. 이 절은 메서드 이름과 인수 목록 뒤와 메서드의 범위를 정의하는 중괄호 앞에 오며, 다음은 예시입니다.
public void writeList() throws IOException, IndexOutOfBoundsException {IndexOutOfBoundsException은 체크되지 않은 예외이므로 throws 절에 포함시키는 것은 필수가 아닙니다. 다음과 같이 작성할 수 있습니다.
public void writeList() throws IOException {
예외를 던지는 방법
예외를 잡으려면 먼저 어딘가에서 예외를 던지는 코드가 있어야 합니다. 내 코드, Java 플랫폼과 함께 제공되는 패키지와 같은 다른 사람이 작성한 패키지의 코드 또는 Java 런타임 환경 등 모든 코드가 예외를 던질 수 있습니다. 예외를 던지는 것이 무엇이든 예외는 항상 throw 문과 함께 던져집니다.
이미 알고 계시겠지만, Java 플랫폼은 수많은 예외 클래스를 제공합니다. 모든 클래스는 Throwable 클래스의 하위 클래스이며, 모두 프로그램 실행 중에 발생할 수 있는 다양한 타입의 예외를 구분할 수 있게 해줍니다.
작성한 클래스 내에서 발생할 수 있는 문제를 나타내는 자체 예외 클래스를 만들 수도 있습니다. 실제로 패키지 개발자의 경우 사용자가 패키지에서 발생할 수 있는 오류를 Java 플랫폼이나 다른 패키지에서 발생하는 오류와 구분할 수 있도록 자체 예외 클래스 집합을 만들어야 할 수도 있습니다.
연쇄 예외를 만들 수도 있습니다. 자세한 내용은 연쇄 예외 섹션을 참조하세요.
Throw 명령문
모든 메서드는 throw 문을 사용하여 예외를 던집니다. throw 문은 하나의 인자, 즉 throw 가능한 객체를 필요로 합니다. Throw 가능 객체는 Throwable 클래스의 모든 서브클래스의 인스턴스입니다. 다음은 throw 문의 예입니다.
throw someThrowableObject;문맥에서 throw 문을 살펴봅시다. 다음 pop() 메서드는 일반적인 스택 객체를 구현하는 클래스에서 가져온 것입니다. 이 메서드는 스택에서 최상위 요소를 제거하고 객체를 반환합니다.
public Object pop() {
Object obj;
if (size == 0) {
throw new EmptyStackException();
}
obj = objectAt(size - 1);
setObjectAt(size - 1, null);
size--;
return obj;
}pop() 메서드는 스택에 요소가 있는지 확인합니다. 스택이 비어 있으면(크기가 0과 같으면), pop은 java.util의 멤버인 새로운 EmptyStackException 객체를 인스턴스화하여 이를 던집니다. 이 장의 예외 클래스 생성하기 섹션에서는 자신만의 예외 클래스를 만드는 방법을 설명합니다. 지금은 java.lang.Throwable 클래스에서 상속하는 객체만 던질 수 있다는 점만 기억하면 됩니다.
pop() 메서드의 선언에는 throws 절이 포함되어 있지 않다는 점에 유의하세요. EmptyStackException은 검사대상이 되는 예외가 아니므로 팝은 예외가 발생할 수 있음을 명시할 필요가 없습니다.
Throw 가능 클래스와 그 서브클래스
Throwable 클래스로부터 상속하는 객체에는 직접 자손(Throwable 클래스로부터 직접 상속하는 객체)과 간접 자손(Throwable 클래스의 자식이나 손자로부터 상속하는 객체)이 있습니다. 아래 그림은 Throwable 클래스와 그 주요 서브클래스의 클래스 계층구조를 보여줍니다. 보시다시피, Throwable에는 두 개의 직계 자손이 있습니다: Error와 Exception.

Throwable 계층구조
Error Class
Java 가상 머신에서 동적 연결 실패 또는 기타 하드 장애가 발생하면 가상 머신은 Error를 던집니다. 간단한 프로그램은 일반적으로 Error 인스턴스를 캐치하거나 던지지 않습니다.
Exception Class
대부분의 프로그램은 Exception 클래스에서 파생된 객체를 던지고 잡습니다. Exception은 문제가 발생했지만 심각한 시스템 문제는 아님을 나타냅니다. 여러분이 작성하는 대부분의 프로그램은 Error와 달리 Exception의 인스턴스를 던지고 잡습니다.
Java 플랫폼은 Exception 클래스의 많은 자손을 정의합니다. 이러한 자손은 발생할 수 있는 다양한 타입의 예외를 나타냅니다. 예를 들어, IllegalAccessException은 특정 메서드를 찾을 수 없음을 알리고, NegativeArraySizeException은 프로그램이 음수 크기의 배열을 만들려고 시도했음을 나타냅니다.
하나의 Exception 서브클래스인 RuntimeException은 API의 잘못된 사용을 나타내는 예외를 위해 예약되어 있습니다. 런타임 예외의 예로는 메서드가 널 참조를 통해 객체의 멤버에 액세스하려고 시도할 때 발생하는 NullPointerException이 있습니다. 확인되지 않은 예외 섹션에서는 대부분의 애플리케이션이 런타임 예외 또는 RuntimeException의 하위 클래스를 던져서는 안 되는 이유에 대해 설명합니다.
연쇄 예외
애플리케이션은 종종 다른 예외를 던져서 예외에 응답합니다. 사실상 첫 번째 예외가 두 번째 예외를 유발하는 것입니다. 하나의 예외가 다른 예외를 유발하는 경우를 파악하는 것은 매우 유용할 수 있습니다. 연쇄 예외는 프로그래머가 이 작업을 수행하는 데 도움이 됩니다.
다음은 연쇄 예외를 지원하는 Throwable의 메서드와 생성자입니다.
Throwable getCause()
Throwable initCause(Throwable)
Throwable(String, Throwable)
Throwable(Throwable)Throwable 인수는 initCause()와 Throwable 생성자의 현재 예외를 발생시킨 예외입니다. getCause()는 현재 예외를 발생시킨 예외를 반환하고 initCause()는 현재 예외의 원인을 설정합니다.
다음 예제는 연쇄 예외를 사용하는 방법을 보여줍니다.
try {
} catch (IOException e) {
throw new SampleException("Other IOException", e);
}이 예제에서는 IOException이 잡히면 원래 원인이 첨부된 새로운 SampleException 예외가 생성되고 예외 체인이 다음 상위 수준의 예외 처리기까지 던져집니다.
스택 추적 정보에 액세스하기
이제 상위 수준의 예외 처리기가 스택 추적을 자체 형식으로 덤프하려고 한다고 가정해 보겠습니다.
정의: 스택 추적은 현재 스레드의 실행 기록에 대한 정보를 제공하고 예외가 발생한 시점에 호출된 클래스 및 메서드의 이름을 나열합니다. 스택 추적은 일반적으로 예외가 발생했을 때 활용할 수 있는 유용한 디버깅 도구입니다.
다음 코드는 예외 객체에서 getStackTrace() 메서드를 호출하는 방법을 보여줍니다.
catch (Exception cause) {
StackTraceElement elements[] = cause.getStackTrace();
for (int i = 0, n = elements.length; i < n; i++) {
System.err.println(elements[i].getFileName()
+ ":" + elements[i].getLineNumber()
+ ">> "
+ elements[i].getMethodName() + "()");
}
}로깅 API
다음 코드 스니펫은 catch 블록 내에서 예외가 발생한 위치를 로깅합니다. 그러나 스택 추적을 수동으로 구문 분석하여 출력을 java.util.logging으로 보내는 대신, java.util.logging 패키지의 로깅 기능을 사용하여 파일로 출력을 전송합니다.
try {
Handler handler = new FileHandler("OutFile.log");
Logger.getLogger("").addHandler(handler);
} catch (IOException e) {
Logger logger = Logger.getLogger("package.name");
StackTraceElement elements[] = e.getStackTrace();
for (int i = 0, n = elements.length; i < n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
}
예외 클래스 만들기
던질 예외의 타입을 선택해야 할 때 다른 사람이 작성한 예외 클래스를 사용하거나(Java 플랫폼은 사용할 수 있는 많은 예외 클래스를 제공합니다), 직접 작성할 수 있습니다. 다음 질문 중 하나라도 ‘예’라고 답했다면 직접 예외 클래스를 작성해야 하며, 그렇지 않다면 다른 사람의 예외 클래스를 사용해도 됩니다.
- Java 플랫폼에서 표현되지 않는 예외 타입이 필요하신가요?
- 다른 공급업체에서 작성한 클래스가 던지는 예외와 여러분의 예외를 구분할 수 있다면 사용자에게 도움이 될까요?
- 코드가 관련 예외를 두 개 이상 던지나요?
- 다른 사람의 예외를 사용하는 경우 사용자가 해당 예외에 액세스할 수 있나요? 비슷한 질문으로 패키지가 독립적이고 자급자족적이어야 하나요?
An Example
linked list 클래스를 작성하고 있다고 가정해 보겠습니다. 이 클래스는 다음과 같은 메서드를 지원합니다:
objectAt(int n)- 목록에서 n번째 위치에 있는 객체를 반환합니다. 인수가 0보다 작거나 현재 목록에 있는 개체 수보다 크면 예외를 throw합니다.firstObject()- 목록에서 첫 번째 객체를 반환합니다. 목록에 개체가 없는 경우 예외를 throw합니다.indexOf(Object o)- 목록에서 지정된 객체를 검색하고 목록에서 해당 객체의 위치를 반환합니다. 메서드에 전달된 객체가 목록에 없으면 예외를 던집니다.
linked list 클래스는 여러 예외를 던질 수 있으며, 하나의 예외 처리기로 연결된 목록에서 던지는 모든 예외를 잡을 수 있으면 편리할 것입니다. 또한 링크된 목록을 패키지로 배포하려는 경우 모든 관련 코드를 함께 패키징해야 합니다. 따라서 링크된 목록은 자체 예외 클래스 집합을 제공해야 합니다.
다음 그림은 링크된 목록에서 던져지는 예외에 대해 가능한 클래스 계층 구조를 보여줍니다.

예외 클래스 계층 구조 예시
수퍼클래스 선택하기
모든 Exception 서브클래스를 LinkedListException의 부모 클래스로 사용할 수 있습니다. 그러나 이러한 하위 클래스를 간략히 살펴보면 너무 전문적이거나 LinkedListException과 전혀 관련이 없기 때문에 부적절하다는 것을 알 수 있습니다. 따라서 LinkedListException의 부모 클래스는 Exception이어야 합니다.
여러분이 작성하는 대부분의 애플리케이션은 Exception의 인스턴스인 객체를 던집니다. Error의 인스턴스는 일반적으로 시스템의 심각한 하드 에러(예: JVM을 실행할 수 없게 하는 에러)에 사용됩니다.
Note: 가독성 있는 코드를 위해 Exception 클래스를 직간접적으로 상속하는 모든 클래스의 이름에 Exception 문자열을 추가하는 것이 좋습니다.