Java에서 부모 클래스의 메서드를 오버라이딩(재정의)할 때, 해당 메서드가 예외를 던진다면 반드시 지켜야 할 몇 가지 규칙이 있습니다. 이 규칙은 다형성(polymorphism)과 깊은 관련이 있는데, 부모 타입의 참조 변수로 메서드를 호출하는 코드가 자식 클래스에서 선언된 더 넓은 범위의 예외를 처리하지 못하는 상황을 방지하기 위함입니다.
오버라이딩 시 예외 처리 규칙
1. 부모 클래스 메서드가 예외를 던지지 않는 경우
부모 클래스 메서드가 어떤 예외도 던지지 않는다면, 자식 클래스에서 오버라이딩한 메서드는 체크드 예외(checked exception)를 던질 수 없습니다. 다만 언체크드 예외(unchecked exception), 즉 RuntimeException 계열의 예외는 자유롭게 던질 수 있습니다.
class Parent {
void doSomething() {
// ...
}
}
class Child extends Parent {
void doSomething() throws IllegalArgumentException {
// ...
}
}2. 부모 클래스 메서드가 체크드 예외를 던지는 경우
부모 클래스 메서드가 하나 이상의 체크드 예외를 던진다면, 자식 클래스 메서드는 동일한 체크드 예외 또는 그 하위(subclass) 예외만 던질 수 있으며, 체크드 예외를 아예 던지지 않거나 언체크드 예외를 던지는 것도 허용됩니다. 부모보다 상위 타입의 새로운 체크드 예외를 추가하는 것은 불가능합니다.
class Parent {
void doSomething() throws IOException, ParseException {
// ...
}
void doSomethingElse() throws IOException {
// ...
}
}
class Child extends Parent {
// 부모와 동일한 예외를 던지는 경우
void doSomething() throws IOException {
// ...
}
// 부모 예외의 하위 예외를 던지는 경우
void doSomethingElse() throws FileNotFoundException, EOFException {
// ...
}
}위 예제에서 FileNotFoundException과 EOFException은 모두 IOException의 하위 클래스이므로 유효한 오버라이딩입니다.
3. 부모 클래스 메서드가 언체크드 예외를 던지는 경우
부모 클래스 메서드의 throws 절에 언체크드 예외가 선언되어 있다면, 자식 클래스 메서드는 예외를 전혀 던지지 않거나, 서로 관련이 없는 예외까지 포함해 어떤 종류의 언체크드 예외든 개수에 상관없이 자유롭게 던질 수 있습니다.
class Parent {
void doSomething() throws IllegalArgumentException {
// ...
}
}
class Child extends Parent {
void doSomething() throws ArithmeticException, BufferOverflowException {
// ...
}
}종합 예제
import java.io.*;
class SuperClassTest {
public void test() throws IOException {
System.out.println("SuperClassTest.test() 메서드");
}
}
class SubClassTest extends SuperClassTest {
// 부모가 체크드 예외를 던지지만,
// 자식은 예외를 던지지 않도록 오버라이딩 가능
public void test() {
System.out.println("SubClassTest.test() 메서드");
}
}
public class OverridingExceptionTest {
public static void main(String[] args) {
SuperClassTest sct = new SubClassTest();
try {
sct.test();
} catch(IOException ioe) {
ioe.printStackTrace();
}
}
}
실행 결과
SubClassTest.test() 메서드
위 예제에서 참조 변수의 타입은 부모 클래스인 SuperClassTest이지만, 실제 객체는 SubClassTest이므로 런타임에는 자식 클래스의 test() 메서드가 호출됩니다. 자식 클래스의 test()는 예외를 던지지 않으므로 try-catch 블록은 아무 문제 없이 정상적으로 동작합니다. 이처럼 컴파일러는 참조 변수의 타입(부모 클래스)을 기준으로 예외 처리 여부를 검사하기 때문에, 자식 클래스가 부모보다 넓은 범위의 체크드 예외를 던지도록 허용하지 않는 것입니다.