다형성 1
- 김영한님의 실전 자바 강의 중 다형성 1 챕터를 학습하며 다형성의 개념, 다형적 참조, 캐스팅, instanceof, 메서드 오버라이딩의 동작 원리를 정리함
다형성(Polymorphism)이란?
기본 개념
-
다형성(Polymorphism)
- “poly”(많은) + “morph”(형태) = “많은 형태”
- 객체지향 프로그래밍의 주요 개념 중 하나
- 하나의 객체가 여러 타입으로 취급될 수 있는 능력
-
다형성의 주요 요소
- 다형적 참조
- 부모 타입의 변수가 자식 인스턴스를 참조할 수 있음
- 메서드 오버라이딩
- 부모 타입으로 호출해도 실제 자식 메서드가 실행됨
- 다형적 참조
다형적 참조
부모 타입으로 자식 인스턴스 참조
1
2
3
4
5
6
7
package instrument.basic;
public class Instrument {
public void play() {
System.out.println("Instrument.play");
}
}
1
2
3
4
5
6
7
package instrument.basic;
public class Piano extends Instrument {
public void tune() {
System.out.println("Piano.tune");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package instrument.basic;
public class PolyMain {
public static void main(String[] args) {
// 부모 변수가 부모 인스턴스 참조
Instrument instrument = new Instrument();
instrument.play();
// 자식 변수가 자식 인스턴스 참조
Piano piano = new Piano();
piano.play(); // 상속받은 메서드
piano.tune(); // 자식 고유 메서드
// 다형적 참조: 부모 변수가 자식 인스턴스 참조
Instrument poly = new Piano();
poly.play();
// poly.tune(); // 컴파일 오류!
}
}
1
2
3
4
Instrument.play
Instrument.play
Piano.tune
Instrument.play
메모리 구조

다형적 참조의 원리
-
가능한 경우
1 2
Instrument poly = new Piano(); // 부모는 자식을 담을 수 있음 Instrument poly = new GrandPiano(); // 부모는 손자도 담을 수 있음
-
불가능한 경우
1
Piano piano = new Instrument(); // 자식은 부모를 담을 수 없음
-
이유
- 부모 타입은 자식 타입을 포함할 수 있음 (더 넓은 범위임)
- 자식 타입은 부모 타입보다 더 많은 기능을 가질 수 있음 (더 구체적임)
호출 가능한 메서드의 제한
1
2
3
Instrument poly = new Piano();
poly.play(); // 호출 가능
poly.tune(); // 컴파일 오류
- 규칙
- 호출 가능한 메서드는 변수의 타입에 의해 결정됨
poly는Instrument타입이므로Instrument에 선언된 메서드만 호출 가능- 실제 인스턴스가
Piano라도Instrument타입으로 선언되었다면tune()는 보이지 않음
다운캐스팅
다운캐스팅의 필요성
1
2
Instrument poly = new Piano();
poly.tune(); // 컴파일 오류
poly는Instrument타입이므로tune()을 호출할 수 없음- 하지만 실제 인스턴스는
Piano이므로tune()가 존재함 - 이럴 때 다운캐스팅을 사용함
다운캐스팅 사용법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package instrument.basic;
public class CastingMain1 {
public static void main(String[] args) {
// 다형적 참조
Instrument poly = new Piano();
// poly.tune(); // 컴파일 오류
// 다운캐스팅 (부모 타입 -> 자식 타입)
Piano piano = (Piano) poly;
piano.tune(); // 호출 가능
}
}
1
Piano.tune
캐스팅 과정 상세 분석
1
Piano piano = (Piano) poly;

- 단계별 과정
poly는Instrument타입이지만x001참조값을 가짐(Piano)캐스팅으로Instrument타입을Piano타입으로 변환piano변수는Piano타입으로x001참조- 이제
piano.tune()호출 가능함
일시적 다운캐스팅
1
((Piano) poly).tune();
- 변수 선언 없이 일시적으로 캐스팅하여 메서드 호출
- 한 번만 사용할 때 유용함
업캐스팅
업캐스팅은 생략 가능
1
2
3
4
5
6
7
Piano piano = new Piano();
// 명시적 업캐스팅
Instrument instrument1 = (Instrument) piano;
// 업캐스팅 생략 (권장)
Instrument instrument2 = piano;
- 원리
- 업캐스팅(자식 → 부모)은 항상 안전함
- 자식 타입은 부모 타입의 모든 기능을 포함하기 때문임
- 따라서 캐스팅 생략 가능함 (자바 컴파일러가 자동 처리)
- 다운캐스팅(부모 → 자식)은 명시적으로 캐스팅이 필요함

다운캐스팅의 위험성
ClassCastException 발생
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package instrument.basic;
public class CastingMain4 {
public static void main(String[] args) {
// 케이스 1: 안전한 다운캐스팅
Instrument instrument1 = new Piano();
Piano piano1 = (Piano) instrument1;
piano1.tune(); // 정상 실행
// 케이스 2: 위험한 다운캐스팅
Instrument instrument2 = new Instrument();
Piano piano2 = (Piano) instrument2; // ClassCastException!
piano2.tune(); // 실행되지 않음
}
}
1
2
3
Piano.tune
Exception in thread "main" java.lang.ClassCastException:
class instrument.basic.Instrument cannot be cast to class instrument.basic.Piano
메모리 구조로 이해하기
-
성공하는 다운캐스팅

-
실패하는 다운캐스팅

다운캐스팅 가능 여부 판단 원칙
-
규칙
new키워드로 생성한 인스턴스의 실제 타입이 기준- 업캐스팅으로 부모 타입 변수에 담겨있더라도, 실제 인스턴스 타입이 중요
-
예시
-
new Piano()로 생성- A, B, C 타입으로 모두 업캐스팅 가능
- A, B, C 타입에서 모두 다시
Piano로 다운캐스팅 가능
-
new Instrument()로 생성Instrument타입으로만 업캐스팅 가능Piano로 다운캐스팅 시도 시ClassCastException발생
-
instanceof 연산자
instanceof의 필요성
1
2
Instrument parent1 = new Instrument();
Instrument parent2 = new Piano();
- 두 변수 모두
Instrument타입 - 하지만
instrument1은 실제로Instrument,instrument2는 실제로Piano - 다운캐스팅 전에 실제 타입을 확인해야 안전함
instanceof 사용법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package instrument.basic;
public class CastingMain5 {
public static void main(String[] args) {
Instrument instrument1 = new Instrument();
System.out.println("instrument1 호출");
call(instrument1);
Instrument instrument2 = new Piano();
System.out.println("instrument2 호출");
call(instrument2);
}
private static void call(Instrument instrument) {
instrument.play();
// instanceof로 타입 확인 후 다운캐스팅
if (instrument instanceof Piano) {
System.out.println("Piano 인스턴스 맞음");
Piano piano = (Piano) instrument;
piano.tune();
}
}
}
1
2
3
4
5
6
7
instrument1 호출
Instrument.play
instrument2 호출
Instrument.play
Piano 인스턴스 맞음
Piano.tune
instanceof 동작 원리
1
2
3
4
5
// instrument1의 경우
instrument instanceof Piano // new Instrument() instanceof Piano → false
// instrument2의 경우
instrument instanceof Piano // new Piano() instanceof Piano → true
-
instanceof 규칙
1 2 3 4
new Instrument() instanceof Instrument // true new Piano() instanceof Instrument // true (자식은 부모의 인스턴스이기도 함) new Instrument() instanceof Piano // false (부모는 자식의 인스턴스가 아님) new Piano() instanceof Piano // true

Java 16 Pattern Matching (참고)
1
2
3
4
5
6
7
8
9
private static void call(Instrument instrument) {
instrument.play();
// instanceof + 다운캐스팅을 한 번에
if (instrument instanceof Piano piano) {
System.out.println("Piano 인스턴스 맞음");
piano.tune();
}
}
- Java 16부터 지원
instanceof검사와 동시에 변수 선언 가능- 코드가 더 간결해짐
다형성과 메서드 오버라이딩
변수와 메서드의 동작 차이
1
2
3
4
5
6
7
8
9
package instrument.overriding;
public class Instrument {
public String value = "parent";
public void method() {
System.out.println("Instrument.method");
}
}
1
2
3
4
5
6
7
8
9
10
package instrument.overriding;
public class Piano extends Instrument {
public String value = "child";
@Override
public void method() {
System.out.println("Piano.method");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package instrument.overriding;
public class OverridingMain {
public static void main(String[] args) {
// 자식 변수가 자식 인스턴스 참조
Piano piano = new Piano();
System.out.println("value = " + piano.value); // child
piano.method(); // Piano.method
// 부모 변수가 부모 인스턴스 참조
Instrument instrument = new Instrument();
System.out.println("value = " + instrument.value); // parent
instrument.method(); // Instrument.method
// 부모 변수가 자식 인스턴스 참조 (다형적 참조)
Instrument poly = new Piano();
System.out.println("value = " + poly.value); // parent ❗
poly.method(); // Piano.method ❗
}
}
1
2
3
4
5
6
value = child
Piano.method
value = parent
Instrument.method
value = parent
Piano.method
원리
-
변수(필드)
- 변수는 선언된 타입을 따름
poly.value→Instrument타입이므로Instrument의value출력
-
메서드
- 오버라이딩된 메서드는 실제 인스턴스의 타입을 따름
poly.method()→ 실제로는Piano인스턴스이므로Piano.method()가 실행됨

-
오버라이딩 우선순위
- 메서드 오버라이딩이 있으면 항상 오버라이딩된 메서드가 우선
- 다형성의 메커니즘
- 이를 통해 부모 타입으로 호출해도 자식의 구체적인 구현이 실행됨
연습 문제
-
객체 지향 프로그래밍에서 ‘다형성’의 의미는 무엇일까요?
-
a. 한 객체가 다양한 형태를 가질 수 있음
- 다형성은 객체 지향의 중요한 특징으로, 하나의 객체가 여러 가지 타입으로 취급될 수 있음을 의미함
- 이는 코드의 유연성과 확장성을 높여줌
-
-
부모 타입 변수가 자식 인스턴스를 참조하고 있을 때, 캐스팅 없이 직접 접근하여 호출할 수 있는 멤버는 무엇일까요?
-
a. 부모 클래스의 멤버만
- 부모 타입 변수는 선언된 타입(부모)의 멤버만 알고 있기 때문에, 실제 참조하는 인스턴스가 자식이더라도 부모 클래스에 정의된 멤버만 직접 호출 가능함
- 자식 고유 멤버는 다운캐스팅이 필요함
-
-
부모 타입 변수가 참조하는 자식 인스턴스의 ‘자식 고유’ 메소드나 필드에 접근하기 위해 주로 사용하는 방법은 무엇일까요?
-
a. 다운캐스팅
- 부모 변수는 자식 고유의 멤버를 직접 알지 못함
- 따라서 부모 타입 변수가 참조하는 자식 인스턴스의 자식 고유 기능을 사용하려면 다운캐스팅을 통해 자식 타입으로 형변환해야 함
-
-
실제 메모리에 부모 타입 인스턴스만 생성되었는데, 이 인스턴스를 자식 타입으로 강제 형변환(다운캐스팅)하려 할 때 발생하는 예외(오류)는 무엇일까요?
-
a. ClassCastException
- 메모리에 존재하지 않는 하위 타입으로 강제로 형변환하려 할 때 ClassCastException이 발생함
- 이는 런타임 오류로 프로그램 실행 중 중단될 수 있으니 주의해야 함
-
-
부모 타입 변수가 자식 인스턴스를 참조하고 있을 때, 부모와 자식 클래스 모두에 동일하게 정의(오버라이딩)된 메소드를 호출하면 어떤 클래스의 메소드가 실행될까요?
-
a. 자식 클래스의 메소드
- 다형성 환경에서 메소드가 오버라이딩되면 변수의 타입이 아닌 실제 인스턴스의 타입에 따라 실행될 메소드가 결정됨
- 오버라이딩된 자식 메소드가 항상 우선권을 가짐
-
요약 정리
주요 개념
-
다형성의 두 기둥
- 다형적 참조
Instrument poly = new Piano()
- 메서드 오버라이딩
- 부모 타입으로 호출해도 자식 메서드가 실행됨
- 다형적 참조
-
캐스팅
- 업캐스팅 (자식 → 부모)
- 생략 가능, 항상 안전
- 다운캐스팅 (부모 → 자식)
- 명시 필요,
instanceof로 확인 권장
- 명시 필요,
- 업캐스팅 (자식 → 부모)
-
instanceof
- 다운캐스팅 전 안전성 확인
- Java 16+ Pattern Matching 지원
-
메서드 오버라이딩
- 오버라이딩된 메서드는 항상 우선 실행
- 변수(필드)는 타입 기준, 메서드는 인스턴스 기준