자바 다형성(Polymorphism)의 개념과 동작 원리

zayn_noh·2026년 5월 15일

Java 개념 정립

목록 보기
3/8
post-thumbnail

다형성(Polymorphism)의 개념

다형성(Polymorphism)이란,
하나의 타입으로 여러 객체를 다룰 수 있도록 하여, 동일한 코드가 상황에 따라 다른 동작을 수행할 수 있게 하는 객체지향의 특성이다.

이러한 다형성의 개념을 글자로만 볼 경우
처음보는 입장에선 무엇을 의미하는지 판단하기에는 너무나 모호하다.

위에 작성한 다형성의 개념을 자바 코드의 예시와 함께 자세히 살펴보도록 하겠다.


예시를 통한 다형성(Polymorphism)의 이해

아래와 같은 상속 구조를 가지는 3개의 클래스를 작성하였다.

// 축구 팀 클래스
class FootballTeam {
	...
	void singAnthem() { // 응원가를 출력한다.
    	System.out.println("~");
    }
    ...
}

// FC Barcelona 축구 팀
class FCBarcelona extends FootballTeam {
	...
    void singAnthem() {
    	System.out.println("Blaugrana al vent~");
    }
    ...
}

// Real Madrid CF 축구 팀
class RealMadridCF extends FootballTeam {
    void singAnthem() {
    	System.out.println("Madrid~ Madrid~ Madrid~");
    }
}

이후 해당 클래스들의 객체를 아래처럼 각각 3개 생성한 후,
각 객체를 가리키는 참조변수를 통해, singAnthem() 메서드를 실행하였다 해보자

FootballTeam fbt0 = new FootballTeam();
FootballTeam fbt1 = new FCBarcelona();
FootballTeam fbt2 = new RealMadridCF();

여기서 주의깊게 보아야 하는 것은 fbt1, fbt2 모두 선언 시에
실제 객체의 클래스명이 아닌 부모 클래스의 클래스명인 FootballTeam을 사용한 것이다.

여기서 처음 다형성의 개념에 대해 작성한 내용 속 해당 부분을 떠올릴 수 있다.

하나의 타입으로 여러 객체를 다룰 수 있도록 하여

  • 하나의 타입: FootballTeam

  • 여러 객체: FootballTeam, FCBarcelona, RealMadridCF

즉 부모-자식 상속 관계를 가지는 클래스들은
실제 인스턴스가 자식 클래스더라도, 이를 참조하는 참조변수의 타입을
부모 클래스로 지정할 수 있는 것이다.

이처럼 자식 객체를 부모 타입의 참조변수로 다루는 것을 업캐스팅(Upcasting)이라 한다.

*주의) 업캐스팅 이후에는 참조변수 타입이 부모 클래스이므로,
부모 클래스에 정의된 멤버만 접근 가능하다.
단, 오버라이딩된 메서드는 런타임 시 실제 객체 기준으로 동작한다.

+) 반대로 자식 클래스의 참조변수가 부모 인스턴스를 참조할 수 있을까?

 FCBarcelona fcb = new FootballTeam();

조금만 생각해봐도 알 수 있겠지만, 당연히 안된다.

자식 클래스는 부모 클래스를 확장한 것으로, 부모보다 많은 기능을 가진다.
따라서 자식 클래스의 타입으로 선언한다는 것은 컴파일러
참조변수 타입을 기준으로 접근 가능한 멤버를 판단한다는 것인데,

막상 객체는 부모 클래스이기에 컴파일 단계에서 에러가 발생한다.
뿐만 아니라 실제론 추가된 기능이 없다하여도

class FCBarcelona extends FootballTeam {
	...
    void singAnthem() {
    	System.out.println("~");
    }
    ...
}

컴파일 단계에서 에러가 발생한다.
-> 이는 컴파일 과정에서 실제 클래스 내부를 살펴보지 않고 타입 관계만 보기 때문

다만 부모 타입으로 업캐스팅된 객체가 실제로 자식 객체인 경우에는,
명시적 형변환을 통해 다시 자식 타입으로 참조할 수 있다.

FootballTeam ft = new FCBarcelona();
FCBarcelona fcb = (FCBarcelona) ft;

이를 다운캐스팅(Downcasting)이라 한다.

다음으로 출력결과를 통해 서로 다른
오버라이딩된 메서드가 실행되는 것을 확인할 수 있는데

fbt0.singAnthem(); // 출력 결과: ~
fbt1.singAnthem(); // 출력 결과: Blaugrana al vent~
fbt2.singAnthem(); // 출력 결과: Madrid~ Madrid~ Madrid~

이는 다형성의 정의에 대해 작성한 글 속에서

동일한 코드가 상황에 따라 다른 동작을 수행할 수 있게

  • 동일한 코드: FootballTeam이 타입인 참조변수가 실행하는 .singAnthem() 메서드

  • 다른 동작: 실제 자손 객체마다 정의된 메서드의 다른 출력 결과

각각 해당 부분에 해당함을 알 수 있다.


런타임 다형성 : (Runtime Polymorphism)

런타임 다형성(Runtime Polymorphism)이란, 자바 어플리케이션 프로그램이
실행되는 시점에 객체지향 특성 중 하나인 다형성이 나타나는 것이다.

이를 좀 더 자세히 풀어보면

런타임 시점에 실제 객체의 타입을 기반으로,
오버라이딩된 메서드가 동적 바인딩되어 실행됨으로써 다형성이 나타난다고 해석할 수 있다.

동적 바인딩(Dynamic Binding)
동적 바인딩이란, 런타임 시점에서 오버라이딩된 메서드 호출 시
컴파일 시점이 아닌 런타임 시점에
실제 객체 타입을 기준으로 실행할 메서드를 결정하는 방식이다.

	FootballTeam fbt0 = new FootballTeam();
	FootballTeam fbt1 = new FCBarcelona();
	FootballTeam fbt2 = new RealMadridCF();

앞선 예제에서 해당 부분이 동적 바인딩의 예시라 할 수 있다.


컴파일 다형성 : (Compile Polymorphism)

컴파일 다형성(Compile Polymorphism)이란,
프로그램이 실행되기 전인 컴파일 시점에 호출할 메서드가 결정되는 다형성을 의미한다.

자바에서는 대표적으로 메서드 오버로딩(Method Overloading) 을 통해
컴파일 다형성이 구현된다.

class FCBarcelona extends FootballTeam {
    void singAnthem() {
    	System.out.println("Blaugrana al vent~");
    }
    
    void singAnthem(String anthemTitle) {
    	System.out.println("Tot~ el camp És~ un clam");
    }
}

위 코드에서 오버로딩된 singAnthem()singAnthem(String anthemTitle)

자바 컴파일러에 의해 메서드 호출 시 전달되는 인자를 분석하여,
어떤 메서드를 호출해야 하는지 컴파일 단계에서 미리 결정된다.

자세히 말하자면 오버로딩된 메서드들의

  • 매개변수 타입
  • 매개변수 개수
  • 참조변수 타입

등을 컴파일러가 분석하여 컴파일 단계에서 호출 대상을 미리 확정한다는 점이다.

이처럼 실행 전에 호출 대상을 확정하는 방식을 정적 바인딩(Static Binding) 이라 한다.


결국 다형성이란

상위 타입의 참조변수를 통해 다양한 하위 객체를 하나의 공통된 방식으로 다룰 수 있으며,

동일한 메서드를 호출하더라도
실제 생성된 객체의 타입에 따라 서로 다른 동작이 수행되는 객체지향의 특성을 의미한다.

즉 개발자는 객체들의 공통된 부모 타입만을 기준으로 코드를 작성할 수 있고
실제 어떤 객체가 전달되었는지에 따라 각 객체에 맞는 기능이 실행된다.

이를 통해 코드의 재사용성과 확장성을 높일 수 있다.

profile
개발 및 IT기술에 대해 정리하고 기록합니다.

0개의 댓글