프로그래밍 언어/Java
[Java] 25. 다형성이란?
WooZzing
2025. 4. 16. 16:52
다형성이란?
✅ 하나의 부모 클래스(또는 인터페이스)로 여러 형태의 자식 객체를 다룰 수 있는 것
즉, 같은 메서드 호출이지만, 객체에 따라 서로 다른 방식으로 동작하는 것을 말합니다.
다형성의 조건
- 상속 (Inheritance) : 부모 클래스를 자식 클래스가 상속받아야 함
- 오버라이딩 (Overriding) : 자식 클래스가 부모의 메서드를 자신에게 맞게 재정의
- 업캐스팅 (Upcasting) : 자식 객체를 부모 타입으로 다룸
다형성의 필요성
필요성 | 예시 |
코드의 재사용성 증가 | 같은 타입으로 다양한 객체 처리 가능 |
유지보수 쉬움 | 객체만 바꿔치기 해도 동작 변경 가능 |
확장성이 뛰어남 | 새로운 클래스가 추가되어도 기존 코드 수정 거의 없음 |
인터페이스 기반 설계 가능 | 전략 패턴, 의존성 주입, 프레임워크 적용 등 실무 핵심에 사용 |
사용 예시
* Animal 클래스 (부모 클래스)
class Animal {
public void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
* Dog 클래스 (자식 클래스)
class Dog extends Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
public void sniff() {
System.out.println("개가 냄새를 맡습니다.");
}
}
* Cat 클래스 (자식 클래스)
class Cat extends Animal {
@Override
public void sound() {
System.out.println("야옹");
}
}
* 실행 클래스
public class Main {
public static void main(String[] args) {
Animal a1 = new Dog(); // 업캐스팅
Animal a2 = new Cat(); // 업캐스팅
a1.sound();
a2.sound();
System.out.println("makeSound 호출");
makeSound(new Dog());
makeSound(new Cat());
}
public static void makeSound(Animal animal) {
animal.sound(); // 실제 객체 기준으로 동작
}
}
- 출력 결과 -
멍멍
야옹
makeSound 호출
멍멍
야옹
- Animal 클래스는 공통 기능인 sound()를 가진 부모 클래스(상위 클래스)입니다.
- sound() 메서드는 기본 동작을 정의하고 있으며, 자식 클래스가 이 메서드를 오버라이딩해서 자기만의 소리를 내도록 할 수 있습니다.
- Dog, Cat 클래스는 Animal을 상속받은 하위 클래스입니다. 자신만의 방식으로 sound() 메서드를 재정의합니다.
사용 예시 코드 분석
위 사용 예시 코드를 하나씩 살펴보겠습니다.
업캐스팅
Animal a1 = new Dog(); // Dog → Animal 타입으로 변환
Animal a2 = new Cat(); // Cat → Animal 타입으로 변환
- 자식 클래스(Dog, Cat)의 객체를 부모 클래스인 Animal 타입으로 참조하는 것
- 업캐스팅은 자동으로 일어남 (명시적 형변환 필요 없음)
➡ 자식은 부모의 일종이기 때문에, 자식 객체를 부모 타입으로 다룰 수 있음
다형성
a1.sound(); // "멍멍"
a2.sound(); // "야옹"
- 참조 변수는 Animal 타입이지만, 실제 객체는 Dog와 Cat
- 자식 클래스에서 오버라이딩한 메서드가 호출됨
- 이를 통해 같은 메서드 호출(sound()), 다른 동작(멍멍 vs 야옹)을 구현 ➡ 다형성
오버라이딩(Overriding)
- 부모 클래스의 메서드를 자식 클래스가 자신에게 맞게 재정의
- @Override는 컴파일 타임에 올바르게 오버라이딩되었는지 체크해줌
다형성의 확장성
public static void makeSound(Animal animal) {
animal.sound();
}
- makeSound() 메서드는 Animal 타입 하나만 받지만
➡ 전달된 객체가 어떤 실제 클래스인지에 따라 동작이 다르게 실행됨 - 이런 구조는 객체 추가 시(예: Bird) 기존 코드를 거의 건드리지 않고도 확장 가능하게 해줌
주의사항
자식 클래스에만 있는 메서드는 사용할 수 없음
✅ 해결 방안은 다운캐스팅 ( instanceof 체크 필수! )
예제
public class Main2 {
public static void main(String[] args) {
Animal a = new Dog(); // 업캐스팅 (부모 타입으로 자식 객체 참조)
a.sound(); // 멍멍 (다형성에 의해 실제 Dog의 sound() 호출)
// 자식 클래스에만 있는 메서드는 사용 불가
// a.sniff(); ❌ 컴파일 에러 발생 (Animal에는 sniff()가 없음)
// instanceof로 실제 타입 확인 후 다운캐스팅
if (a instanceof Dog) {
Dog dog = (Dog) a; // 다운캐스팅
dog.sniff(); // "개가 냄새를 맡습니다." 출력
}
}
}
❌ a는 Animal 타입이라서, sound()는 쓸 수 있지만, Dog 클래스에만 정의된 sniff()는 보이지 않음 ➡ 호출 불가
✅ 해결 방법 - 다운캐스팅
if (a instanceof Dog) {
Dog dog = (Dog) a; // Animal → Dog로 다운캐스팅
dog.sniff(); // 이제 sniff() 호출 가능
}
- instanceof는 안전한 다운캐스팅을 위한 전제 조건입니다.
- 타입이 맞는지 확인 없이 캐스팅하면 ClassCastException이 발생할 수 있기 때문입니다.
👉 업캐스팅된 객체는 부모 타입만 보이기 때문에, 자식만 가진 기능을 쓰려면 다운캐스팅이 필요하며, 이때 instanceof로 타입 체크를 통해 안전하게 사용해야 합니다.
요약 정리
항목 | 내용 |
개념 | 하나의 타입(부모/인터페이스)으로 여러 객체(자식)를 다룰 수 있음 |
전제조건 | 상속 또는 인터페이스 구현 |
장점 | 유지보수 용이, 코드 유연성, 확장성 뛰어남 |
필수 요소 | 업캐스팅, 오버라이딩, instanceof, 다운캐스팅 |
읽어주셔서 감사합니다 😊