09. 다형성
다형성(polymorphism) 은 같은 메시지(메서드 호출) 가 객체 종류에 따라 다른 동작을 하도록 만드는 OOP 의 핵심입니다. 부모 타입 변수에 다양한 자식 객체를 담아 두고, 호출 시점에 알맞은 구현이 자동 선택됩니다.
이 강의에서 배우는 것
- 1업캐스팅(자식→부모) 과 다운캐스팅(부모→자식) 의 의미를 안다
- 2JDK 16+ `instanceof` 패턴 매칭 문법을 사용한다
- 3`abstract` 클래스로 미완성 부모를 정의한다
- 4정적 타입과 동적 타입의 차이를 이해한다
소개
다형성(polymorphism) 은 같은 메시지(메서드 호출) 가 객체 종류에 따라 다른 동작을 하도록 만드는 OOP 의 핵심입니다. 부모 타입 변수에 다양한 자식 객체를 담아 두고, 호출 시점에 알맞은 구현이 자동 선택됩니다.
핵심 개념
1) 업캐스팅과 다운캐스팅
Animal a = new Dog("바둑이"); // 업캐스팅 (자동)
Dog d = (Dog) a; // 다운캐스팅 (명시적)업캐스팅은 항상 안전합니다. 다운캐스팅은 실제 타입이 맞아야 하고, 아니라면 `ClassCastException` 이 납니다.
2) `instanceof` 패턴 매칭
if (a instanceof Dog d) {
d.bark(); // 이미 캐스팅된 변수
}JDK 16+ 의 패턴 매칭은 타입 검사 + 캐스팅 + 새 변수 선언을 한 번에 합니다.
3) `abstract` 클래스
public abstract class Shape {
public abstract double area(); // 본문 없음
public void describe() {
System.out.println("area=" + area());
}
}- 본문이 없는 `abstract` 메서드를 한 개라도 가지면 클래스 자체도 `abstract` 여야 합니다
- `new` 로 직접 생성 불가 — 반드시 자식이 구현
- 일반 메서드와 추상 메서드를 혼합 가능 (인터페이스와 차이점)
4) 정적 타입 vs 동적 타입
Animal a = new Dog("바둑이");
// 정적 타입: Animal, 동적 타입: Dog
a.speak(); // Dog.speak() 가 호출됨 (동적 디스패치)핵심 예제
예제 1 — `Polymorphism.java` : 업/다운캐스팅
public class Polymorphism {
static class Animal {
void speak() { System.out.println("..."); }
}
static class Cat extends Animal {
@Override void speak() { System.out.println("야옹"); }
void purr() { System.out.println("그르릉"); }
}
public static void main(String[] args) {
Animal a = new Cat(); // 업캐스팅
a.speak(); // 야옹 (동적 디스패치)
// a.purr(); // 컴파일 에러: Animal 에는 없음
Cat c = (Cat) a; // 다운캐스팅
c.purr();
}
}**실행 결과**
야옹
그르릉**메모:** 부모 타입은 자식의 추가 메서드를 모르므로, 자식 전용 호출엔 다운캐스팅이 필요합니다.
예제 2 — `InstanceOfPattern.java` : 패턴 매칭
public class InstanceOfPattern {
sealed interface Animal permits Dog, Cat, Fish {}
record Dog(String name) implements Animal {}
record Cat(String name) implements Animal {}
record Fish(String name) implements Animal {}
static String introduce(Animal a) {
if (a instanceof Dog d) return d.name() + "는 강아지";
if (a instanceof Cat c) return c.name() + "는 고양이";
if (a instanceof Fish f) return f.name() + "는 물고기";
return "?";
}
public static void main(String[] args) {
Animal[] zoo = { new Dog("바둑이"), new Cat("나비"), new Fish("니모") };
for (Animal a : zoo) System.out.println(introduce(a));
}
}**실행 결과**
바둑이는 강아지
나비는 고양이
니모는 물고기**메모:** `sealed` 와 record 를 결합하면 자식 목록을 명시적으로 제한할 수 있어 안전합니다.
예제 3 — `AbstractShape.java` : 추상 클래스
public class AbstractShape {
static abstract class Shape {
abstract double area();
void describe() { System.out.println("area=" + area()); }
}
static class Square extends Shape {
double side;
Square(double s) { this.side = s; }
@Override double area() { return side * side; }
}
static class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
@Override double area() { return Math.PI * r * r; }
}
public static void main(String[] args) {
// new Shape(); // 컴파일 에러: abstract
Shape[] xs = { new Square(2), new Circle(3) };
for (Shape s : xs) s.describe();
}
}**실행 결과**
area=4.0
area=28.274333882308138**메모:** 추상 클래스는 **공통 동작 + 자식이 채울 빈칸** 을 함께 둘 때 알맞습니다.
예제 4 — `SwitchPattern.java` : switch 패턴 매칭 (JDK 21)
public class SwitchPattern {
sealed interface Pet permits DogPet, CatPet {}
record DogPet(String name, int age) implements Pet {}
record CatPet(String name) implements Pet {}
static String describe(Pet p) {
return switch (p) {
case DogPet d -> d.name() + "(" + d.age() + "살)";
case CatPet c -> c.name();
};
}
public static void main(String[] args) {
System.out.println(describe(new DogPet("바둑이", 3)));
System.out.println(describe(new CatPet("나비")));
}
}**실행 결과**
바둑이(3살)
나비**메모:** `sealed` 와 `switch` 패턴이 만나면 컴파일러가 **모든 경우** 가 처리됐는지 검증해 줍니다.
전체 예제 코드 (src/)
src/AbstractShape.java
public class AbstractShape {
static abstract class Shape {
abstract double area();
void describe() { System.out.println("area=" + area()); }
}
static class Square extends Shape {
double side;
Square(double s) { this.side = s; }
@Override double area() { return side * side; }
}
static class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
@Override double area() { return Math.PI * r * r; }
}
public static void main(String[] args) {
Shape[] xs = { new Square(2), new Circle(3) };
for (Shape s : xs) s.describe();
}
}
src/InstanceOfPattern.java
public class InstanceOfPattern {
sealed interface Animal permits Dog, Cat, Fish {}
record Dog(String name) implements Animal {}
record Cat(String name) implements Animal {}
record Fish(String name) implements Animal {}
static String introduce(Animal a) {
if (a instanceof Dog d) return d.name() + "는 강아지";
if (a instanceof Cat c) return c.name() + "는 고양이";
if (a instanceof Fish f) return f.name() + "는 물고기";
return "?";
}
public static void main(String[] args) {
Animal[] zoo = { new Dog("바둑이"), new Cat("나비"), new Fish("니모") };
for (Animal a : zoo) System.out.println(introduce(a));
}
}
src/Polymorphism.java
public class Polymorphism {
static class Animal {
void speak() { System.out.println("..."); }
}
static class Cat extends Animal {
@Override void speak() { System.out.println("야옹"); }
void purr() { System.out.println("그르릉"); }
}
public static void main(String[] args) {
Animal a = new Cat();
a.speak();
Cat c = (Cat) a;
c.purr();
}
}
src/SwitchPattern.java
public class SwitchPattern {
sealed interface Pet permits DogPet, CatPet {}
record DogPet(String name, int age) implements Pet {}
record CatPet(String name) implements Pet {}
static String describe(Pet p) {
return switch (p) {
case DogPet d -> d.name() + "(" + d.age() + "살)";
case CatPet c -> c.name();
};
}
public static void main(String[] args) {
System.out.println(describe(new DogPet("바둑이", 3)));
System.out.println(describe(new CatPet("나비")));
}
}
자주 하는 실수
- 다운캐스팅 전에 `instanceof` 검사 생략 → `ClassCastException`
- `@Override` 누락으로 오버라이딩이 아니라 새 메서드가 됨
- `abstract` 메서드에 본문을 적음
- 추상 클래스를 `new` 로 직접 인스턴스화 시도
- `sealed` 자식이 같은 모듈/패키지에 있어야 한다는 제약 망각
정리
- 부모 타입에 자식 객체를 담는 것이 다형성의 출발
- `instanceof` 패턴 매칭은 가독성을 크게 높여 줌
- `abstract` 와 `sealed` 는 "허용된 자식만" 만들 때 유용
과제
# 과제 - 09. 다형성
## 문제 1 — `Vehicle` 다형성
- 파일명: `Homework01.java`
- 핵심 개념: 추상 부모 + 자식 오버라이딩
요구사항
- 추상 클래스 `Vehicle` 에 `String describe()` 추상 메서드.
- 자식 `Car`, `Bike` 가 각자 구현.
- `Vehicle[]` 배열로 호출.
예상 출력
Car 4 wheels
Bike 2 wheels## 문제 2 — `instanceof` 패턴 매칭
- 파일명: `Homework02.java`
- 핵심 개념: JDK 16+ 패턴 매칭
요구사항
- `Object` 타입 변수에 정수, 문자열, 리스트(List) 를 넣어 가며 다음을 출력:
- `Integer` 면 `숫자: <값>`
- `String` 면 `문자열: <길이>자`
- 그 외엔 `알 수 없음`
예상 출력
숫자: 42
문자열: 5자
알 수 없음## 정답 확인 직접 풀어 본 후 [`answer/`](./answer/) 폴더의 정답과 비교해 보세요.
정답 코드 (homework/answer/)
answer/Homework01.java
/** Vehicle 다형성. */
public class Homework01 {
public static void main(String[] args) {
Vehicle[] vs = { new Car(), new Bike() };
for (Vehicle v : vs) {
System.out.println(v.describe());
}
}
}
abstract class Vehicle {
abstract String describe();
}
class Car extends Vehicle {
@Override String describe() { return "Car 4 wheels"; }
}
class Bike extends Vehicle {
@Override String describe() { return "Bike 2 wheels"; }
}
answer/Homework02.java
import java.util.List;
/** instanceof 패턴 매칭. */
public class Homework02 {
public static void main(String[] args) {
Object[] objs = { 42, "Hello", List.of(1, 2, 3) };
for (Object o : objs) {
describe(o);
}
}
static void describe(Object o) {
if (o instanceof Integer n) {
System.out.println("숫자: " + n);
} else if (o instanceof String s) {
System.out.println("문자열: " + s.length() + "자");
} else {
System.out.println("알 수 없음");
}
}
}
직접 해 보기
cd 02_객체지향/09_다형성/src
javac Polymorphism.java
java Polymorphism다음 단원
[10_인터페이스](../10_인터페이스/) — `interface`, `default` 메서드, 함수형 인터페이스를 배웁니다.