프로그래밍 언어/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, 다운캐스팅

 

 

 

 

읽어주셔서 감사합니다 😊

 


소스 코드 바로가기