이전 글에서 자바의 오버라이드(Override)의 원리에 대해 다뤄봤는데 이번에는 조금 더 나아가서
오버라이드는 어떤 종류의 메서드에서 가능하고 안되는 메서드는 어떤것이 있는지 논해보겠습니다.
우선 정답을 먼저 적어보자면
이렇게 세가지 종류의 메서드는 오버라이드가 불가능합니다.
자 그렇다면 위의 메서드들에서는 어째서 오버라이드가 불가능한 것 일까요?
우선 이전글에서 사용한 예제를 가져와서 이 주제를 논해보겠습니다.
class SuperClass {
int x;
public void f() {
System.out.println("SuperClass");
}
}
class SubClass extends SuperClass {
@Override
public void f() {
System.out.println("SubClass");
}
}
public class Test {
public static void main(String[] args) {
SuperClass superClass = new SubClass();
superClass.f();
}
}
출력결과
SubClass
오버라이드의 원리에 의해 하위타입의 인스턴스를 생성할 때 상위타입의 필드 및 메서드를 정의하기 위한 공간과 하위타입의 필드 및 메서드를 정의하기 위한 공간이 할당되고 JVM
의 동적 바인딩
에 의하여 하위타입의 f()
메서드가 호출되어 SubClass
가 출력되게 됩니다.
그렇다면 만약에 정의되어있는 메서드가 private
메서드인 경우에는 무슨일이 일어나게 될까요??
참고) 상위 하위 타입의 접근지정자가 다르면 다른 메서드로 인식하여 오버라이드가 불가능합니다.
즉, 오버라이드를 하기 위해서는 우선 메서드의 리턴타입 뿐만 아니라 접근지정자도 일치시켜 주는것이 전제입니다.
class SuperClass {
private void g() {
System.out.println("SuperClass");
}
}
class SubClass extends SuperClass {
@Override
private void g() {
System.out.println("SuperClass");
}
}
위와 같은 코드를 작성하게 되면
다음과 같이 오버라이드 할 수 없다는 컴파일 에러를 띄워주게 됩니다.
그렇다면 왜 이런 현상이 발생하게 되는걸까요??
메서드 오버라이드의 원리는
컴파일 시점
에컴파일러
가정적 바인딩
을 통해 상위 타입의 메서드로 인식을 합니다.- 그 이후 프로그램
런타임 시점
에JVM
이 인스턴스를 생성하게 되고.- 그 인스턴스 안에 하위 타입의 메서드가 재정의 되어있으면 기존
정적 바인딩
된 메서드 호출문을 대신 하위 타입의 메서드를 호출하는 것으로 대체하는 식으로 실행이 됩니다.
그리고 이 메서드 호출을 대체하는 과정을 동적 바인딩
이라고 하였구요.
하지만, 지금 정의한 메서드가 private
이기 떄문에 이야기가 조금 달리집니다.
잠시 private
접근 지정자에 대해 간단하게 짚어보면
private
은 인스턴스에 접근하는 변수로 무조건 인스턴스 타입의 형과 동일한 형으로만 접근이 가능합니다.
그러면 다시 오버라이드된 메서드의 호출 과정을 다시 보게 되면
상위 타입의 참조변수로 메서드를 호출하면
SubClass
형의 인스턴스 공간안의SuperClass
형의g()
로 접근이 가능합니다. 하지만동적바인딩
에 의해 하위타입에 정의되어있는g()
를 호출하기 때문에 하위타입에 있는private
메서드g()
에는 접근이 불가능한 것이죠.
그렇다면 이번에는 하위 타입의 참조변수로 SubClass
인스턴스를 참조하는 경우에는 어떻게 될까요?
SubClass
타입의 인스턴스안의SuperClass
의 공간에 접근 해서 메서드들을 찾아보는데g()
가private
이기 때문에 참조변수가g()
에 접근할 수가 없습니다.
따라서 private
메서드는 오버라이드가 불가능합니다.
이번에는 static
메서드를 한번 살펴봅시다.
스태틱 메서드는 객체 인스턴스
에 종속되는 개념이 아닌 클래스
에 종속 되는 개념입니다.
맨 처음에 메모리 공간에 딱 한개만 생성되며 호출시에도 JVM
이 객체를 찾아서 호출하는 것이 아니라 컴파일 시점에 선언된 타입의 메서드를 호출하게 됩니다.
즉, 위에서 설명한 동적 바인딩
을 구조적으로 해줄 수가 없는 것이죠.
이에 대해 잘 설명되어 있는 블로그 링크를 추가 설명으로 남깁니다.
출처 [JAVA] 정적 메소드를 오버라이드 하지 못하는 이유
예약어 final
은 특정 변수나 메서드 혹은 클래스를 변화하지 못하게, 즉 상수로 만들어주는 역할도 하지만
final
자체가 클래스간 상속 금지
와 메소드 오버라이딩의 금지
를 담당하는 키워드 이기 때문에 불가능합니다.
클래스의 메서드 중 일부 또는 전부를 final 로 선언할 수 있습니다 .
메서드 선언에서 키워드를 사용하여 final메서드를 하위 클래스로 재정의할 수 없음을 나타냅니다. 클래스 Object는 이 작업을 수행합니다.
여러 메서드는 변경해서는 안 되는 구현이 있고 개체의 일관된 상태에 중요한 경우 메서드를 최종으로 만들고 싶을 수 있습니다.
출처 오라클 자바 api 공식문서 final 키워드
그동안 자바를 사용할 때 상속을 많이 사용을 하긴 했지만, 정작 이것의 매커니즘에는 딱히 고민해본적이 없던거 같습니다.
특히, 동적, 정적의 바인딩
개념과 힙 공간안에 인스턴스가 생성됐을때 그 인스턴스안에 상위, 하위 타입을 위한 공간이 생성된다는 것이 좀 충격이었네요.
한 인스턴스 안에 상위 하위 타입의 공간이 각각 생성되어서 위와 같은 것도 가능하다는,,