이전에 객체지향의 4가지 특성에 대해 정리해본적이 있다. 그 중에서도 다형성에 대해서 다시 한번 정리할 필요에 따라 글을 적어 가보고자 한다.
💡 왜 만들어졌는가?
기존에 알고있던 구현방식을 동물의 소리를 출력하는 과정을 예시로 들어 보자면
public class AnimalManager {
public static void main(String[] args) {
Dog dog = new Dog("바둑이");
Cat cat = new Cat("나비");
Bird bird = new Bird("짹이");
// 각각 따로따로 처리해야 함
dog.makeSound();
cat.makeSound();
bird.makeSound();
}
}
새로운 동물이 추가될 때마다 코드를 일일히 추가해줘야 하는 부분과, 반복문으로 한번에 처리할 수 없고 일일히 추가되기 때문에 코드가 엄청나게 길어지는 상황이 생기게 된다. 이를 해결하기 위해 다형성의 개념과 활용으로 문제를 보다 쉽게 풀어나아갈 수 있다.
💡 업캐스팅 : 부모 타입으로 자식 객체를 참조하기
: 공통된 부분을 부모로 묶어 객체를 여러 타입으로 만들 수 있음
부모 클래스 만들기
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void makeSound() {
System.out.println("동물 소리~");
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println(name + ": 멍멍!");
}
}
class Cat extends Animal {
Cat(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println(name + ": 야옹~");
}
}
class Bird extends Animal {
Bird(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println(name + ": 짹짹!");
}
}
생성 이후의 변화는 아래와 같다.
public class PolymorphismTest {
public static void main(String[] args) {
//부모 타입으로 자식 객체를 담을 수 있다!
Animal animal1 = new Dog("바둑이");
Animal animal1 = new Dog("나비");
Animal animal1 = new Dog("짹이");
// 아래와 같이 가능해진다!
System.out.pirntln("타입 확인:");
System.out.pirntln("animal1 instanceof Animal"); // true
System.out.pirntln("animal1 instanceof Dog"); // true
}
}
- Animal animal = new Dog(); 는 문법적으로 이상이 없음
- 자식은 부모의 모든 것을 가지고 있으므로 부모 타입에 담을 수 있음
💡 오버라이딩 : 런타임 메서드 결정
같은 메서드 호출, 다른 결과를 도출
public class PolymorphismMagic {
public static void main(String[] args) {
Animal animal1 = new Dog("바둑이");
Animal animal2 = new Cat("야옹이");
Animal animal3 = new Bird("짹이");
// 모두 Animal 타입이지만
animal1.makeSound(); // 바둑이: 멍멍!
animal2.makeSound(); // 야옹이: 야옹~
animal3.makeSound(); // 짹이: 짹짹!
}
}
- 주의깊게 확인해야 할 부분
- 변수 타입은 모두 Animal
- 하지만 실행되는 메서드는 실제 객체(Dog, Cat, Bird)의 메서드
이런 과정이 다형성의 특징
배열로 한번에 처리
public class ArrayPolymorphism {
public static void main(String[] args) {
// 배열에 담을 수 있다!
Animal[] animals = new Animal[3];
anmals[0] = new Dog("바둑이");
anmals[1] = new Dog("야옹이");
anmals[2] = new Dog("짹이");
System.out.println("=== 모든 동물의 소리 ===");
for (Animal animal : animals) {
animal.makeSound();
}
//출력:
//바둑이: 멍멍!
//나비: 야옹~
//짹이: 짹짹!
}
}
👉 다형성을 잘 활용만 한다면 비즈니스 로직은 유지한 채로 새로운 객체를 추가하는 것만으로도 전반적인 기능구현을 그대로 추가할 수 있다는 것!
메서드 매개변수로도 활용
public class PolymorphismParameter {
// 모든 동물을 받을 수 있는 메서드
public static void checkAnimal(Animal animal) {
System.out.println(animal.name + "건강검진 중...");
animal.makeSound();
System.out.println("검진 완료!\n");
}
public static void main(String[] args) {
Dog dog = new Dog("바둑이");
Cat cat = new Cat("야옹이");
Bird bird = new Bird("짹이");
// 하나의 메서드로 모든 동물 처리!
checkAnimal(dog);
checkAnimal(cat);
checkAnimal(bird);
}
}
Before (다형성 없이)
public static void checkDog(Dog dog) {...}
public static void checkCat(Cat cat) {...}
public static void checkBird(Bird bird) {...}
// 동물 100종이면 메서드 100개를 작성해야 함
After (다형성 사용)
public static void checkAnimal(Animal animal) { ... }
// 끝! 모든 동물 처리 가능
💡다형성의 정의와 원리
다형성이란(Polymorphism) 이란?
Poly(많은) + Morph(형태) = 여러 형태를 가진다.
Animal animal = new Dog("바둑이");
- 이 변수는 Animal 타입이기도 하고
- 동시에 Dog 객체이기도 함
- 하나의 객체가 여러 타입으로 취급될 수 있다.
다형성의 3가지 조건
1. 상속 관계(extends)
2. 메서드 오버라이딩 (@Override)
3. 부모 타입으로 자식 객체 참조
💡 다형성의 장점
장점1
새로운 동물 추가하기:
// 새로운 동물 추가
class Rabbit extends Animal {
Rabbit(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println(name + ": 깡총깡총!");
}
}
public clss ExtensibilityTest {
public static void main(String[] args) {
Animal[] animals = {
new Dog("바둑이"),
new Cat("야옹이"),
new Bird("짹이"),
new Rabbit("토순이") // 새로 추가
};
// 기존 코드 수정 없이 그대로 작동
for(Animal animal : animals) {
animal.makeSound();
}
}
}
👉 새로운 클래스만 만들면 되고 기존 코드는 건드릴 필요 없음
장점2 : 코드 중복 제거
Before(다형성 없이):
public class AnimalManager {
List<Dog> dogs = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
List<Bird> birds = new ArrayList<>();
void addDog(Dog dog) { dogs.add(dog); }
void addCat(Cat cat) { dogs.add(cat); }
void addBird(Bird bird) { dogs.add(bird); }
void soundAllDogs() {
for (Dog dog : dogs) dog.makeSound();
}
void soundAllCats() {
for (Cat cat : cats) cat.makeSound();
}
void soundAllBirds() {
for (Bird bird : birds) bird.makeSound();
}
}
After (다형성 사용):
public class AnimalManager {
List<Animal> animals = new ArrayList<>();
void addAnimal(Animal animal) {
animals.add(animal);
}
void soundAll() {
for (Animal animal : animals) {
animal.makeSound();
}
}
}
Before vs After 비교 정리
| 항목 | 다형성 없이 | 다형성 사용 |
| 새 타입 추가 | 모든 관련 코드 수정 | 새 클래스만 추가 |
| 코드 중복 | 타입마다 메서드 필요 | 하나의 메서드로 통합 |
| 유지보수 | 어려움 (변경 영향 큼) | 쉬움 (독립적) |
| 코드 라인 수 | 많음 | 적음 |
💡 주의할 점들
1. 부모 타입으로는 자식 고유 메서드 호출 불가
class Dog extends Animal {
Dog(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println(name + ": 멍멍!");
}
// Dog만의 고유 메서드
void wagTail() {
System.out.println(name + "가 꼬리를 흔듭니다!");
}
}
public class LimitationTest {
public static void main(String[] args) {
Dog dog = new Dog("바둑이");
dog.wagTail(); // OK! Dog 타입이니까
Animal animal = new Dog("뭉치");
// animal.wagTail(); // 컴파일 에러!
// Animal 타입에는 wagTail() 메서드가 없음
animal.makeSound(); // OK! Animal에 있는 메서드
}
}
👉 이유
- 컴파일러는 변수의 선언된 타입(Animal)만 봄
- Animal 클래스에 wagTail()이 없으면 호출 불가
- 실제 객체가 Dog여도 상관없음!
2. 형변환(Casting)이 필요한 경우
public class CastingExample {
public static void main(String[] args) {
Animal animal = new Dog("바둑이");
// 자식 고유 메서드를 호출하려면 형변환 필요
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 다운캐스팅
dog.wagTail(); // 이제 가능!
}
// 더 간단하게 (Java 16+)
if (animal instanceof Dog dog) {
dog.wagTail();
}
}
}
3. 잘못된 형변환은 런타임 에러!
public class WrongCasting {
public static void main(String[] args) {
Animal animal = new Cat("나비");
// 위험한 코드!
// Dog dog = (Dog) animal; // ClassException 발생!
// 안전하게 확인하고 반환
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.wagTail();
} else {
System.out.println("이 동물은 Dog가 아닙니다!");
}
}
}
4. 업캐스팅 vs 다운캐스팅
public class CastingTypes {
public static void main(String[] args) {
//업캐스팅 (자동, 안전)
Dog dog = new Dog("바둑이");
Animal animal = dog; // 자동 변환, 괄호 불필요
//다운캐스팅 (수동,위험)
Animal animal2 = new Dog("뭉치");
Dog dog2 = (Dog) animal2; // 명시적 변환 필요
//잘못된 다운캐스팅
Animal animal3 = new Cat("나비");
// Dog dog3 = (Dog) animal3; // 런타임 에러!
}
}
⭐️ 기억할 것!
- 업캐스팅 (자식→부모): 항상 안전, 자동 변환
- 다운캐스팅 (부모→자식): 위험할 수 있음, 반드시 instanceof로 확인!
핵심 정리
다형성의 핵심 3줄 요약
1. 부모 타입으로 자식 객체를 다룰 수 있다.
2. 같은 메서드 호출이 객체에 따라 다르게 동작한다.
3. 코드의 유연성과 확장성이 크게 향상된다.
반드시 기억할 것
- ✅ 업캐스팅은 항상 안전
- ⚠️ 다운캐스팅은 instanceof로 먼저 확인
- ✅ 오버라이딩된 메서드는 실제 객체 기준으로 실행
- ⚠️ 부모 타입으로는 자식 고유 메서드 호출 불가
'JAVA' 카테고리의 다른 글
| [JAVA] 불변객체(Immutable Object) (0) | 2025.10.13 |
|---|---|
| [JAVA] 예외처리 (1) | 2025.10.13 |
| [JAVA Algorithm] 컬렉션 프레임워크 (0) | 2025.10.02 |
| [Java Algorithm] 2차원 배열 (0) | 2025.10.02 |
| [JAVA Algorithm] 배열 개념 정리 (0) | 2025.10.01 |