메서드 오버라이딩에서 예외 선언의 범위

CJI0524·2024년 7월 9일
0

Java/Class

목록 보기
3/8

1. 오버라이딩시 메서드의 접근 제어자 변경 제한

부모클래스로부터 상속받은 메서드의 오버라이딩 (overriding) 시 조상 클래스의 메서드보다 더 넓은 범위의 예외 선언을 할 수 없다. 라는 제한이 있다. 예를 들면 조상클래스의 메서드에 선언된 예외가 IOException이라면 자식클래스에서 해당 메서드를 오버라이딩 시 예외는 동일한 예외(IOException)이나 IOException의 하위 타입 (= IOException의 자식클래스)만 선언이 가능하다는 의미이다.

Java에서 메서드를 오버라이딩할 때 예외를 처리하려면 세 가지 중요한 규칙을 따라야 한다. 이 규칙들은 다음과 같다.

1. 부모 클래스의 메서드가 throws 절을 사용하여 예외를 던지지 않는 경우

  • 오버라이딩 메서드는 어떤 체크드 예외 (Checked Exception)도 던질 수 없음.
  • 오버라이딩 메서드는 모든 언체크드 예외 (Unchecked Exception)를 던질 수 있음.

2. 부모 클래스의 메서드가 체크드 예외를 던지는 경우

  • 자식 클래스 메서드는 부모 클래스 메서드의 예외의 하위 클래스인 예외를 던질 수 있음.
  • 자식 클래스 메서드는 부모 클래스 메서드의 예외의 상위 클래스인 예외를 던질 수 없음.
  • 자식 클래스 메서드는 모든 언체크드 예외를 던질 수 있음.

3. 부모 클래스의 메서드가 언체크드 예외를 던지는 경우

  • 오버라이딩 메서드는 모든 언체크드 예외를 던질 수 있음.
  • 오버라이딩 메서드는 오버라이딩된 메서드가 던지는 동일한 예외를 던질 수 있음.
  • 오버라이딩 메서드는 메서드 수준 예외를 무시할 수 있음. (즉 예외를 던지지 않음)

다음은 이러한 제한을 간략히 표현한 그림이다.

2. 왜 이러한 제한이 있을까?

2.1. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)

리스코프 치환 원칙(Liskov Substitution Principle, LSP)은 객체 지향 프로그래밍에서 중요한 원칙 중 하나로, 바바라 리스코프(Barbara Liskov)가 1987년에 제안한 원칙이다. LSP는 "자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있어야 한다"는 개념을 포함한다. 이는 자식 클래스가 부모 클래스의 기능을 확장하거나 구체화하더라도 부모 클래스의 계약을 준수해야 한다는 의미이다. 쉽게 표현하면 프로그램에서 부모 클래스를 자식 클래스로 변경해도 정상적으로 동작해야 한다는 것이다.

이 원칙은 객체 지향 설계에서 상속을 사용하는 경우 특히 중요하며, 올바르게 적용되었을 때 코드의 재사용성과 유지보수성을 높일 수 있다. 리스코프 치환 원칙에 대해서는 추후 객체 지향 프로그래밍(Object-Oriented Programming, OOP)과 관련된 게시글에서 좀 더 자세히 다루기로 한다.

2.2. 리스코프 치환 원칙과 오버라이딩에 따른 예외 선언 제한의 관계

리스코프 치환 원칙(LSP)에 따르면, 자식 클래스는 부모 클래스를 대체할 수 있어야 한다. 하지만 부모클래스로부터 상속받은 메서드의 오버라이딩 시 부모 클래스의 메서드보다 더 넓은 예외 선언을 할 경우 이러한 원칙을 위배하게 된다.

다음의 예시를 보자

✍️ 작성

import java.io.IOException;
import java.sql.SQLException;

// 부모 클래스
class Parent {
    void performAction() throws IOException {
        // 부모 클래스의 메서드는 IOException만 던질 수 있음
        throw new IOException("Parent exception");
    }
}

// 자식 클래스
class Child extends Parent {
    @Override
    void performAction() throws IOException, SQLException {
        // 자식 클래스의 메서드는 IOException뿐만 아니라 SQLException도 던질 수 있음
        throw new SQLException("Child exception");
    }
}

// 클라이언트 코드
public class Main {
    public static void main(String[] args) {
        Parent parent = new Child();

        try {
            parent.performAction();
        } catch (IOException e) {
            System.out.println("Caught IOException");
        }
        // SQLException을 catch하는 블록이 없으므로 런타임 예외 발생 가능
    }
}

위 코드에서 parent는 Parent 타입이지만 실제로는 Child 클래스의 인스턴스이다. 부모 클래스의 performAction() 메서드는 IOExcepion의 예외만 선언하고 있으므로 IOException만 예외 처리를 해주면 된다. 그러나 자식 클래스에서 performAction() 메서드를 오버라이딩할 때, SQLException의 예외를 추가했으므로 자식 클래스의 performAction()을 쓰기 위해서는 SQLException도 예외처리를 해야한다. 그렇지 않을 경우 에러가 발생한다. 이는 곧 Parent 인스턴스에 performAction()를 호출하는 코드에서 Child 인스턴스가 호출하는 걸로 대체하면 에러가 발생한다는 의미이다. 즉 자식 클래스 (Child)가 부모 클래스 (Parent)를 대체하지 못하는 상황이 된 것이다.


따라서 결과적으로 다음과 같은 에러가 나온다.


🖥️ 결과

Exception in thread "main" java.sql.SQLException: Child exception
    at Child.performAction(Main.java:16)
    at Main.main(Main.java:23)

3. 해당 게시글 작성에 참고한 글 목록

자바의 정석 3판 (저자 : 남궁성)
Scientech Easy : Exception Handling with Method Overriding in Java
[OOP] 객체지향 5대 원칙(SOLID) - 리스코프 치환 원칙 (LSP)

(※ 추후 계약에 의한 설계 (Contract By Design) 개념도 추가하여 서술한다.)

profile
개발돌이

0개의 댓글