티스토리 뷰
[Java] 부모 클래스의 메소드 오버라이딩이 더 큰 범위의 접근 제어자만 가능한 이유 or 더 좁은 범위로 변경할 수 없는 이유
망나니개발자 2022. 3. 23. 10:00이번에는 자바 언어에서 부모 클래스의 메소드를 오버라이딩 할 때 더 큰 범위의 접근 제어자만 가능한 이유 또는 더 좁은 범위로 변경할 수 없는 이유에 대해 알아보도록 하겠습니다. 기본적이지만 상당히 중요한 내용인만큼 꼭 이해를 하고 넘어가면 좋을 것 같습니다!
1. 부모 클래스의 메소드 오버라이딩이 더 큰 범위의 접근 제어자만 가능한 이유 or 더 좁은 범위로 변경할 수 없는 이유
[ 예시 코드로 상황 소개 ]
예를 들어 다음과 같은 Animal 클래스와 public으로 선언된 bark 메소드가 있다고 하자.
public class Animal {
public void bark() {
System.out.println("동물이 짖는다.");
}
}
그리고 Animal 클래스를 상속받는 Dog 클래스가 있다고 하자. Jav에서 자식 클래스에서 부모 클래스의 메소드를 오버라이딩할 때 더 큰 범위의 접근 제어자만 설정이 가능하다. 즉, 더 좁은 범위로 메소드를 오버라이딩하는 것은 불가능하며 위의 경우 public 이므로 오버라이딩 할 때에는 public만 가능하다.
public class Dog extends Animal {
@Override
public void bark() {
System.out.println("강아지가 짖는다.");
}
}
만약 부모 클래스의 메소드가 protected 였다면 오버라이딩 할 때에는 protected 또는 public만 오버라이딩이 가능하다. 만약 더 좁은 범위로 오버라이딩하려고 하면 다음과 같은 컴파일 에러가 발생하게 된다.
에러 메세지를 분석해보면 부모 메소드는 public인데 더 좁은 범위의 protected로 변경을 시도한다는 것이다. 그렇다면 자바 언어가 이러한 제약을 갖는 이유는 무엇인지 한번 살펴보도록 하자.
[ 더 좁은 범위로 오버라이딩이 불가능한 이유 ]
아시다시피 자바는 객체지향 언어이다. 그리고 자바 언어가 이러한 제약을 갖는 이유 역시 객체지향 및 다형성과 관련이 있다. 예를 들어 생성된 Dog 객체를 부모 클래스인 Animal로 반환받아서 사용하는 클라이언트가 있다고 하자.
class DogTest {
@Test
void animalBark() {
Animal animal = returnAnimal();
animal.bark();
}
private Dog returnDog() {
return new Dog();
}
}
클라이언트는 반환된 Animal 클래스에 선언된 bark 메소드가 public이므로 bark를 호출할 수 있다. 그리고 마찬가지로 우리는 구체 클래스를 반환받아서 동일하게 부모 메소드를 호출할 수 있다. 이러한 코드는 객체지향 언어에서 흔하게 볼 수 있는 부분이며 SOLID의 리스코프 치환 원칙(Liskov Substitution Principle, LSP)에 해당하기도 한다.
class DogTest {
@Test
void dogBark() {
Dog dog = returnDog();
dog.bark();
}
private Dog returnDog() {
return new Dog();
}
}
그렇다면 여기서 잠시 멈추어 자식 클래스인 Dog에서 오버라이딩한 메소드가 더 좁은 범위라면 어떻게 될지 생각을 해보자.
만약 bark 메소드를 protected로 오버라이딩한다면 클라이언트는 Dog 클래스를 반환받을땐 bark 호출이 불가능해지는 반면에 부모 클래스인 Animal로 반환받으면 호출이 가능해지는 기이한 현상이 발생하게 된다. 그리고 이는 bark 메소드가 protected인 상태에서 private로 오버라이딩경우도 동일하다.
위와 같은 현상은 객체지향 프로그래밍(OOP)의 기본 원칙인 리스코프 치환 원칙에 위배된다. 리스코프 치환 원칙은 하위 타입은 상위 타입을 대체할 수 있어야 한다는 것이기 때문이다. 위의 예제를 바탕으로 클라이언트는 자식 클래스를 반환받으면 메소드 호출이 불가능해지게 된다. 그래서 자바는 언어 차원에서 메소드 오버라이딩을 할 때 더 좁은 범위의 접근제어자로 변경할 수 없도록 제약을 주었다. 이를 통해 자식 클래스는 부모 클래스와 동일한 퍼블릭 인터페이스를 제공하여 리스코프 치환 원칙을 준수할 수 있게 되었다.
'Java & Kotlin' 카테고리의 다른 글
[Java] 익명 객체(Anonymous Object)를 통해 인터페이스와 추상 클래스의 객체를 바로 생성하기 (2) | 2022.04.11 |
---|---|
[Java] Private 메소드를 테스트하는 방법과 이를 지양해야 하는 이유 (2) | 2022.04.09 |
[Java] try-with-resources란? try-with-resources 사용법 예시와 try-with-resources를 사용해야 하는 이유 (18) | 2022.03.07 |
[Java] 언제 Optional을 사용해야 하는가? 올바른 Optional 사용법 가이드 - (2/2) (34) | 2022.01.02 |
[Java] 메소드 오버라이딩/ 메소드 오버로딩을 통한 상속 다형성에 대한 이해와 Self 참조 (0) | 2021.10.15 |