Java 다형성 완벽 가이드: 실전 예제와 함께
Java에서 다형성이란?
다형성(Polymorphism)은 Java 객체지향 프로그래밍(OOP)의 핵심 개념입니다. 클래스들이 상속으로 연결될 때 발생하며, 단일 인터페이스가 서로 다른 내부 형태를 표현할 수 있게 합니다. 결과적으로 메서드 호출이 런타임의 실제 객체 타입에 따라 다른 구현을 호출할 수 있습니다.
음성 통화, 문자, 이미지, 이메일로 소통할 수 있는 스마트폰을 생각해 보세요. 목표인 '소통'은 같지만 각 방식은 다르게 동작합니다. 이것이 일상에서의 다형성입니다.
학습 내용
- Java 다형성 정의
- 실전 OOP 예제
- 메서드 오버라이딩
- 오버로딩 vs 오버라이딩
- 동적(런타임) 다형성
- 정적 vs 동적 다형성
super 키워드
OOP에서의 Java 다형성 예제
Account 기본 클래스가 deposit()과 withdraw()를 제공한다고 가정합니다. SavingsAccount와 CheckingAccount 두 서브클래스는 연산이 동일하므로 이 메서드들을 그대로 상속받습니다.

변경 요구사항에 적응하기
오버드래프트 기능 같은 새 기능이 필요하면, PrivilegedAccount 서브클래스가 withdraw()를 오버라이드하여 기존 서브클래스 수정 없이 새 로직을 제공합니다. 이는 다형성의 유지보수 이점을 보여줍니다.

Java 메서드 오버라이딩
오버라이딩은 서브클래스가 슈퍼클래스에 이미 정의된 메서드의 자체 구현을 제공할 때 발생합니다. 메서드 시그니처(이름, 매개변수, 반환 타입)는 정확히 일치해야 합니다.
핵심 규칙
- 오버라이드된 메서드는 더 넓은 가시성(예:
protected → public)을 가질 수 있지만, 더 좁힐 수는 없음
- 반환 타입은 동일하거나 공변적이어야 함
예제:
class Doctor {
public void treatPatient() {
// 공통 치료 로직
}
}
class Surgeon extends Doctor {
@Override
public void treatPatient() {
// 특화된 수술 치료
}
}
public class Run {
public static void main(String[] args) {
Doctor doctor = new Doctor();
doctor.treatPatient(); // Doctor.treatPatient() 실행
Surgeon surgeon = new Surgeon();
surgeon.treatPatient(); // Surgeon.treatPatient() 실행
}
}
오버로딩 vs 오버라이딩
| 메서드 오버로딩 | 메서드 오버라이딩 |
| 같은 클래스, 같은 이름, 다른 매개변수 리스트 |
슈퍼클래스 메서드를 서브클래스에서 동일한 시그니처로 재정의 |
예시:void sum(int a, int b); void sum(int a, int b, int c); void sum(float a, double b); |
예시:class X { int sum() { ... } }
class Y extends X {
@Override int sum() { ... }
} |
동적 다형성 (런타임)
슈퍼클래스 참조가 서브클래스 객체를 가리킬 때, JVM은 메서드 호출을 런타임에 결정합니다. 이것이 실제 다형성의 작동입니다.
Doctor doc = new Surgeon();
doc.treatPatient(); // Surgeon.treatPatient() 실행
실제로 호출되는 메서드는 참조 타입이 아닌 객체의 런타임 타입에 따라 결정됩니다.
정적 vs 동적 다형성
정적 다형성(오버로딩)은 컴파일 타임에 결정되고, 동적 다형성(오버라이딩)은 런타임에 결정됩니다.
| 정적 다형성 | 동적 다형성 |
| 메서드 오버로딩; 컴파일 타임 바인딩 |
메서드 오버라이딩; 런타임 바인딩 |
예시:void sum(int a, int b); void sum(float a, double b); |
예시:Doctor d = new Surgeon(); d.treatPatient(); |
super 키워드 사용
서브클래스가 슈퍼클래스 메서드를 확장해야 할 때, super로 부모 구현을 호출한 후 자체 로직을 추가합니다.
class Surgeon extends Doctor {
@Override
public void treatPatient() {
super.treatPatient(); // 기본 로직
// 외과 의사 전용 로직
}
}
마찬가지로 super로 슈퍼클래스의 protected 또는 public 필드에 접근할 수 있습니다.
실전 예제: 상속, 다형성, Super
아래 코드를 IDE에 복사하여 출력을 관찰해 보세요.
public class Test {
public static void main(String[] args) {
X x = new X();
Y y = new Y();
y.m2();
// 상속 효과 보기 위해 주석 해제
// x.m1();
// y.m1();
// x = y; // 부모 참조가 자식 객체 가리킴
// x.m1();
// y.a = 10; // 에러: a는 private
}
}
class X {
private int a;
int b;
public void m1() {
System.out.println("This is method m1 of class X");
}
}
class Y extends X {
int c;
@Override
public void m1() {
System.out.println("This is method m1 of class Y");
}
public void m2() {
super.m1();
System.out.println("This is method m2 of class Y");
}
}
핵심 요약
- 다형성은 상속을 통해 객체가 여러 형태를 취할 수 있게 함
- 메서드 오버라이딩은 시그니처를 유지하면서 서브클래스에서 동작 변경
- 동적 다형성은 런타임에 메서드 호출을 결정하여 유연한 코드 가능
- 정적 다형성(오버로딩)은 컴파일 타임, 동적 다형성은 런타임
super 키워드로 서브클래스가 부모 메서드 호출 또는 protected 멤버 접근 가능