본문 바로가기

JAVA

[JAVA] 다형성과 중요한 개념들.

💫 다형성(Polymorphism)

좋은 개발자가 되기 위해서는 다형성에 대한 이해가 필수적이다. 다형성은 이름 그대로 다양한 형태, 여러 형태를 뜻한다. 프로그래밍에서의 다형성은 한 객체가 여러 타입의 객체로 취급될 수 있는 능력을 의미한다. 보통 하나의 객체는 하나의 타입으로 고정되어 있지만, 다형성을 통해 하나의 객체가 다른 타입으로 사용될 수 있다는 뜻이다.

➡️ 다형적 참조

  • 부모 타입 변수가 부모 인스턴스를 참조:Parent 타입에서 객체를 생성했으므로 메모리상에는 부모 인스턴스만 생성된다.
  • Parent parent = new Parent();
  • 자식 타입 변수가 자식 인스턴스를 참조:자식 타입에서 생성하면 메모리상에는 부모와 자식 인스턴스가 모두 생성된다.
  • Child child = new Child();
  • 다형적 참조: 부모 타입 변수가 자식 인스턴스를 참조:Parent 타입의 poly 변수에 Child 타입 객체를 생성했기 때문에 메모리상에 부모와 자식 모두 생성되고, 참조값이 poly에 담긴다.
  • Parent poly = new Child();

이것이 바로 다형적 참조다. 자바에서는 부모 타입이 자식 타입을 담을 수 있다. 하지만 그 반대인 자식 타입은 부모 타입을 담을 수 없다.

하지만 poly는 Parent 타입이기 때문에 자식의 기능을 직접 호출할 수는 없다. (poly를 호출하면 부모 타입에서 기능을 찾기 때문에, 자식 기능은 찾을 수 없다.) 상속 관계에서는 부모 방향으로 찾으러 올라갈 수 있지만 자식 방향으로는 찾을 수 없는 한계가 있기 때문이다.

하지만 우리는 자식 타입으로 생성했으니 분명히 부모와 자식 인스턴스가 같이 존재한다. 그럼 여기서 자식 기능을 호출하려면 어떻게 해야 할까? 바로 캐스팅이 필요하다.


➡️ 다형성과 캐스팅

다형적 참조의 한계점을 해결하는 방법이 바로 다운캐스팅이다. (부모 타입 → 자식 타입으로 타입을 변경하는 것)

Child child = (Child) poly;

괄호 안에 Child를 명시하여 호출 타입을 자식으로 바꿔주면 된다.

하지만 캐스팅한다고 해서 parent poly의 타입이 변하는 것은 아니다.

캐스팅의 종류:

  • 업캐스팅: 자식 타입 → 부모 타입으로 변경 (부모 타입은 자식을 담을 수 있으므로 보통 생략 가능하다.)
  • 다운캐스팅: 부모 타입 → 자식 타입으로 변경 (개발자가 명시적으로 캐스팅을 해야 한다.)

⚠️ 다운캐스팅의 주의점

다운캐스팅은 심각한 런타임 오류를 발생시킬 수 있다.

  • Child 타입의 인스턴스를 생성해서 Parent 타입 변수에 넣어두고 나중에 다운캐스팅하는 것은 문제없다.
  • 하지만 만약 Parent 타입의 인스턴스를 생성해서 Parent 타입 변수에 넣고 다운캐스팅하면, IDE 상에서는 문제가 안 보이지만 실행하면 런타임 오류(ClassCastException)가 발생한다. 왜냐하면 부모 타입으로 인스턴스를 생성하면 자식 타입은 아예 생성되지 않은 상태이기 때문에 찾을 수가 없기 때문이다.

🔒 업캐스팅이 안전하고 다운캐스팅이 위험한 이유

  • 업캐스팅: 객체 생성 과정에서 해당 타입의 상위 부모 타입과 자신 타입이 모두 함께 생성된다. 따라서 위로만 타입을 변경하는 경우 메모리상에 인스턴스가 모두 존재하므로 안전하고, 캐스팅을 생략할 수 있다.
  • 다운캐스팅: 인스턴스에 존재하지 않는 하위 타입으로 캐스팅하는 문제가 발생할 수 있다. (상위 타입 객체로 생성하는 경우 하위 객체가 생성되지 않는다.) 개발자는 이 문제를 인지하고 사용해야 한다는 의미로 명시적인 캐스팅이 필요한 것이다.

🚨 컴파일 오류와 런타임 오류

  • 컴파일 오류: 변수명의 오타, 잘못된 클래스 이름 사용 등 자바 프로그램 실행 전 컴파일 과정에서 발생하는 오류다. IDE에서 바로 캐치해서 수정할 수 있으므로 안전한 오류라고 볼 수 있다.
  • 런타임 오류: 자바 프로그램 실행 후 시점에서 발생하는 오류로, 매우 안 좋은 오류다. 이 프로그램 실행 과정에서 사용자에게 직접 오류를 발생시키기 때문이다.

🔎 instanceof

참조형 변수는 다양한 자식을 대상으로 참조할 수 있는데, 참조하는 대상이 어떤 인스턴스를 참조하고 있는지 확인하는 방법이 바로 instanceof 키워드다. 주로 다운캐스팅을 안전하게 하기 위해 참조 대상이 변경 가능한 인스턴스인지 확인한 후 다운캐스팅을 진행한다.

if (poly instanceof Child) {
    Child child = (Child) poly; // 안전한 다운캐스팅
    // ...
}

🤝 다형성과 메서드 오버라이딩

다형성 이론에서 매우 중요한 핵심 이론이다. 오버라이딩된 메서드가 항상 우선권을 가진다는 점을 꼭 기억해야 한다.

부모 변수가 자식 인스턴스를 참조하는 다형적 참조 시에도 오버라이딩된 메서드가 항상 우선권을 가진다. 만약 자식에서 오버라이딩하고 손자에서도 오버라이딩한다면, 가장 하위의 오버라이딩 메서드가 우선권을 가진다.


🚀 다형성 활용

  • 다형적 참조를 통해 Animal 변수는 Dog, Cat, Cow 등 자식 인스턴스의 참조가 가능해진다. (부모는 자식을 담을 수 있다.)
  • 메서드 오버라이딩 덕분에 부모 클래스 기능을 호출해도 오버라이딩 메서드가 우선권을 가지므로 자식 클래스의 인스턴스 호출이 가능해진다.
  • 배열과 for문을 사용해서 다형성을 적용할 수 있다. Animal 타입의 배열을 만들어서 그 안에 Dog, Cat, Cow 객체를 직접 생성하여 넣은 다음, for문으로 돌려서 호출하는 메서드에 다형적 참조 개념을 이용해 참조값을 던져주면 오버라이딩된 메서드가 호출된다. 이렇게 하면 코드의 중복과 반복 문제를 깔끔하게 해결할 수 있다.

🚧 다형적 활용의 문제점

  • 부모 타입 객체를 직접 인스턴스 생성하는 경우: Animal이라는 추상적인 클래스가 있고, 자식 클래스로는 Dog, Cat, Duck 등이 있다고 가정해 보자. 여기서 Animal 객체의 인스턴스를 직접 생성할 일이 있을까? Dog, Cat, Duck은 실제 존재하는 동물이지만, Animal이라는 추상적인 개념이 실제로 존재하는 것은 이상하다.
  • 하지만 Animal도 클래스이기 때문에 인스턴스 생성 및 사용에 제약이 없다. 누군가 이 Animal 클래스를 직접 인스턴스 생성하여 작동할 수는 있지만, 그것이 정상적인 작동이라고는 할 수 없다. 부모 클래스의 인스턴스를 생성하면 자식 클래스의 인스턴스가 메모리에 생성이 안 되기 때문에, 여기서 자식 객체의 메서드를 호출한다면 당연히 컴파일 오류가 발생할 것이고, 여기서 다운캐스팅까지 시도한다면 런타임 오류까지 발생할 수 있다.
  • 메서드 오버라이딩을 하지 않는 경우: 만약 상속받은 객체에 메서드 오버라이딩을 해야 하는데 실수로 재정의하는 것을 잊었다고 해보자. 다형적 참조를 활용해서 부모 타입 변수에 자식 타입의 인스턴스 참조값을 넘겨주고 메서드를 호출했는데, 원래는 부모 타입 메서드를 호출하면 메서드 오버라이딩 된 것이 우선권을 가지므로 당연히 자식 타입에서 재정의한 메서드가 호출될 줄 알았는데 부모 기능이 그대로 호출되어 버린다. 이는 코드상으로 전혀 문제가 없기 때문에 개발자 입장에서는 의아할 수 있다.

'JAVA' 카테고리의 다른 글

[JAVA] 인터페이스란?  (0) 2025.09.25
[JAVA] 추상 클래스와 메서드  (0) 2025.09.24
[JAVA] 상속과 super  (0) 2025.09.20
[JAVA] final이란?  (0) 2025.09.17
[JAVA] static에 대하여 알아보자.  (0) 2025.09.16