객체지향개념에서 다형성이란 여러 가지 형태를 가질 수 있는 능력을 의미하는데, 자바에서는 부모클래스의 타입의 참조변수로 자식클래스의 인스턴스를 참조할 수 있음을 의미한다.
너무 추상적이게 들릴 것 같아서 추가적인 설명을 해보면 다형성은 부모-자식 상속 관계에서 부모 클래스가 동일한 호출로 자식 클래스들을 다르게 동작시키는 객체 지향 원리라고 할 수 있다.
이 다형성을 활용하면 부모 클래스가 자식 클래스가 어떻게 구현되어 있는지 몰라도 오버라이딩을 통해 자식 클래스에 접근할 수 있게 된다.
Q> 부모 클래스는 자식 클래스가 무슨 일을 하는지 몰라도 어떻게 자식 클래스의 메서드를 호출시킬 수 있을까?
A> 이유는 동적 바인딩 덕분이다.
동적 바인딩을 설명하기에 앞서 다들 잘 아는 오버라이딩(Overriding)과 오버로딩(Overloading)에 대해 잠시 설명을 하자면
위 두 개념은 그리 어렵지 않은 내용이라 크게 어렵지 않을 듯한데 오버라이딩과 오버로딩은 실행될 때 차이점이 있다.
그 차이는 바로 오버라이딩과 오버로딩된 동명의 메서드들 중 실제 사용할 메서드를 결정하는 시점이다.
런타임 시점/컴파일 시점 또는 런타임 에러/컴파일 에러이라는 용어를 한 번 쯤은 들어봤을 것이라 생각된다. 런타임 시점과 컴파일 시점에 대해 보다 쉬운 이해를 위해 런타임 에러와 컴파일 에러를 예시로 들면,
java.lang.ArithmeticException
)나 NullPointException
과 같이 프로그램이 동작되어야 알 수 있는 오류이다.다시 본론으로 돌아가서, 본문 처음에 다형성은 무엇인가에 써놓은 부분을 보면
"다형성을 활용하면 부모 클래스가 자식 클래스가 어떻게 구현되어 있는지 몰라도 오버라이딩을 통해 자식 클래스에 접근할 수 있게 된다."
라고 정의했다.
그리고 이것이 가능한 이유가 동적 바인딩(Dynamic binding)때문이라고 했는데,
우선, 바인딩(Binding)은 프로그램에서 사용될 변수나 메서드를 결정짓는 행위를 뜻한다.
그렇다면 동적 바인딩은 다음과 같이 정의할 수 있다.
동적 바인딩(Dynamic Binding): 런타임 시점. 즉, 파일을 실행하는 시점에 성격이 결정되는 행위
즉, 동적 바인딩은 런타임 시점에 실제로 실행될 메서드를 호출하는 것
그래서 흔히 오버라이딩은 동적 바인딩과, 오버로딩을 정적 바인딩과 연관 짓는 이유가 이러한 이유 때문이다.
동적 바인딩과 오버라이딩이 어떻게 연관되어 있는지 약간의 이해를 돕기 위해 간단한 코드 예시를 추가했다.
예시 코드를 UML 다이어그램으로 표현하게 되면 아래와 같은 형태로 나타낼 수 있는데 단순히 ChildClazz extends ParentClazz
를 표현한 다이어그램이다.
코드는 다음과 같다.
public class PolyParentChild {
public static void main(String[] args) {
// point 1
ParentClazz parentClazz = new ParentClazz();
parentClazz.prt(); // ParentClazz.prt
// point 2
ChildClazz childClazz = new ChildClazz();
childClazz.prt(); // ChildClazz.prt
System.out.println("==========");
// point 3
ParentClazz polyClazz = new ChildClazz();
polyClazz.prt(); // ChildClazz.prt
}
}
class ParentClazz {
public void prt() {
System.out.println("ParentClazz.prt");
}
}
class ChildClazz extends ParentClazz{
@Override
public void prt() {
System.out.println("ChildClazz.prt");
}
}
[실행결과]
ParentClazz.prt
ChildClazz.prt
==========
ChildClazz.prt
위 코드에서 ChildClazz
는 ParentClazz
를 상속받고 있고, 서로 부모-자식 관계로 이루어져 있다.
point1
과 point2
는 인스턴스의 타입과 일치하는 타입으로 참조변수를 생성했기 때문에 prt()
메서드를 호출할 경우 각각의 클래스에 정의된 prt()
메서드가 실행되게 된다.
point1: ParentClazz.prt
point2: ChildClazz.prt
이제 다형성으로 넘어가서, 다형성은 "자바에서는 부모클래스의 타입의 참조변수로 자식클래스의 인스턴스를 참조할 수 있다."고 앞서 설명드린 바 있다.
ParentClazz
와 ChildClazz
는 부모-자식 관계이기 때문에 부모클래스의 타입(ParentClazz
)으로 자식클래스의 인스턴스(ChildClazz
)를 참조할 수 있다.
point3
이 다형성의 예시에 해당하는 부분이고, 부모의 타입으로 자식의 인스턴스를 참조 가능하기에 해당 부분의 코드가 ParentClazz polyClazz = new ChildClazz();
로 되어있는 것을 알 수 있다.
그리고 이 참조변수로(다형성이 적용된) prt()
메서드를 호출하게 되면 자식 클래스에 의해 오버라이딩된 prt메서드가 호출됨을 확인할 수 있다.
즉, 다형성에 의해 부모 클래스의 참조변수로 prt메서드를 호출했지만, 자식 클래스에서 재정의된 prt메서드가 호출됐다.
이처럼 다형성은 실행되기 전에는 어떤 메소드를 호출할 지 컴파일 시점에는 알 수 없고, 실행이 되어야만(런타임) 실제로 어떤 메서드를 호출할 지 결정할 수 있다.
이런 방식으로 다형성을 잘 활용하면 동적 바인딩에 의해 자식 클래스에서 오버라이딩된 메서드를 호출할 수 있게 된다.
김영한 님의 스프링 MVC 1편 강의를 듣기 전까지 개념은 이해했지만 원리는 잘 이해되지 않았는데 섹션 4. MVC 프레임워크 만들기 에 DispacherServlet을 약식으로 만들어 보면서 다형성에 대해 원리와 개념을 완전히 이해했다.
특히, HandlerAdapter(interface)를 구현할 때 해당 Controller를 지원하는 인스턴스인지 확인하기 위해 supports
가 필요했는데 이 부분에서 다형성에 대해 확실히 알게 된 것 같다.
코드의 일부를 첨부하자면,
@WebServlet(name = "frontControllerServletVx", urlPatterns = "/front-controller/vx/*")
public class FrontControllerServletVx extends HttpServlet {
...
public FrontControllerServletVx() {
initHandlerMappingMap();
initHandlerAdapters();
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
...
@Override
protected void service(...) throws ServletException, IOException {
// URL 매핑
Object handler = getHandler(request);
if(handler == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// support()
MyHandlerAdapter adapter = getHandlerAdapter(handler);
...
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if(adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException(...);
}
...
public class ControllerVxHandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supports(Object handler) {
// ControllerVx의 인스턴스 중 하나인가?
return (handler instanceof ControllerVx);
}
...
전체 코드가 아니기에 이해하기 어려울 수 있을 것 같다고 생각하지만.. 모든 코드를 올릴 수는 없는 노릇이라 support
와 관련된 코드는 이정도로 정리할 수 있을 것 같다.
[참고]
오버로딩 & 오버라이딩의 결정 시점 차이 (Overloading & Overriding)
[JAVA] 자바 다형성(POLYMORPHISM) 개념부터 응용 쉬운 설명
다형성과 동적 바인딩이 담겨있는 짤막한 코드
[JAVA] 자바 다형성 기본 및 활용