관리 메뉴

나구리의 개발공부기록

다형성, 다형성 시작, 다형성과 캐스팅, 캐스팅의 종류, 다운캐스팅과 주의점, instanceof, 다형성과 메서드 오버라이딩 본문

인프런 - 실전 자바 로드맵/실전 자바 - 기본편

다형성, 다형성 시작, 다형성과 캐스팅, 캐스팅의 종류, 다운캐스팅과 주의점, instanceof, 다형성과 메서드 오버라이딩

소소한나구리 2025. 1. 12. 21:04

출처 : 인프런 - 김영한의 실전 자바 - 기본편 (유료) / 김영한님  
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용


1. 다형성 시작

1) 다형성

(1) 설명

  • 객체지향 프로그래밍의 대표적인 특징으로는 캡슐화, 상속, 다형성이 있는데 그 중에서 다형성을 객체지향 프로그래밍의 꽃이라 불림
  • 캡슐화나 상속은 직관적으로 이해하기가 쉬운 반면에 다형성은 제대로 이해하는 것과 활용하는 것이 어려운데 좋은 개발자가 되기 위해서는 다형성에 대한 이해가 필수임
  • 다형성(Polymorphism)은 이름 그대로 다양한 형태, 여러 형태를 뜻하는데 프로그래밍에서는 한 객체가 여러 타입의 객체로 취급될 수 있는 능력을 뜻함
  • 보통 하나의 객체는 하나의 타입으로 고정되어 있는데 다형성을 사용하면 하나의 객체가 다른 타입으로 사용될 수 있다는 의미임
  • 다형성을 이해하기 위해서는 크게 다형적 참조, 메서드 오버라이딩 2가지 핵심 이론을 알아야 함

2) 다형적 참조

(1) Parent

package poly.basic;

public class Parent {
    public void parentMethod() {
        System.out.println("Parent.parentMethod");
    }
}

 

(2) Child

package poly.basic;

public class Child extends Parent {
    public void childMethod() {
        System.out.println("Child.childMethod");
    }
}

 

(3) PolyMain

  • 부모 타입 변수로 부모 인스턴스를 참조, 자식 타입 변수로 자식 인스턴스를 참조, 부모 타입 변수로 자식 인스턴스를 참조하는 코드를 작성
  • 자식 타입 변수로 부모 인스턴스를 참조하는 코드는 컴파일 오류가 발생함
  • 부모 타입 변수로 자식의 기능은 호출할 수 없음
package poly.basic;

public class PolyMain {
    public static void main(String[] args) {

        // 부모 변수가 부모 인스턴스 참조
        System.out.println("Parent -> Parent");
        Parent parent = new Parent();
        parent.parentMethod();

        // 자식 변수가 자식 인스턴스 참조
        System.out.println("Child -> Child");
        Child child = new Child();
        child.parentMethod();
        child.childMethod();

        // 부모 변수가 자식 인스턴스 참조
        System.out.println("Parent -> Child");
        Parent poly = new Child();
        poly.parentMethod();

//        Child child1 = new Parent();    // 컴파일 오류, 자식은 부모를 담을 수 없음

        // 부모 타입 변수로 자식의 기능은 호출할 수 없음, 컴파일 오류 발생
//        poly.childMethod();   // 호출 불가

    }
}
/* 실행 결과
Parent -> Parent
Parent.parentMethod
Child -> Child
Parent.parentMethod
Child.childMethod
Parent -> Child
Parent.parentMethod
*/

 

(4) 부모 타입의 변수가 부모 인스턴스 참조

  • Parent parent = new Parent()로 부모 타입인 Parent로 Parent 인스턴스를 생성했으므로 메모리 상에 Parent만 생성되며 자식은 생성되지 않음
  • 생성된 참조값을 Parent 타입의 변수인 parent에 담아두었으므로 parent.parentMethod()로 호출하면 Parent 클래스의 parentMethod()메서드가 호출됨

(5) 자식 타입의 변수가 자식 인스턴스 참조

  • Child child = new Child()로 자식 타입인 Child로 Child 인스턴스를 생성했기 때문에 메모리 상에 Child와 Parent가 모두 생성됨
  • 생성된 참조값을 Child 타입의 변수인 child에 담아두고 child.childMethod()를 호출하면 인스턴스의 Child 클래스에 있는 childMethod()가 호출됨

(6-1) 다형적 참조: 부모 타입의 변수가 자식 인스턴스 참조

  • Parent poly = new Child()로 부모 타입인 Parent로 Child 인스턴스를 만들었음
  • 이 경우에도 자식 타입인 Child를 생성했기 때문에 메모리 상에 Child와 Parent가 모두 생성되며 생성된 참조값을 Parent 타입의 변수인 poly에 담아둠
  • 자바에서 부모 타입은 자식 타입을 담을 수 있어서 이런 방식이 허용되지만 반대로 자식 타입은 부모 타입을 담을 수 없어서 Child child1 = new Parent()는 컴파일 오류가 발생함

(6-2) 다형적 참조

  • 지금까지 학습한 내용을 떠올려 보면 항상 같은 타입에 참조를 대입하여 한 가지 형태만 참조할 수 있었음
  • 그러나 Parent 타입의 변수는 자신인 Parent는 물론이고 자식 타입까지 참조할 수 있으며 만약 손자까지 있다면 그 손자도, 그 하위 타입도 모두 참조할 수 있음
  • 즉, 자바에서 부모 타입은 자신은 물론 자신을 기준으로 모든 자식 타입을 참조할 수 있는데 이것이 바로 다양한 형태를 참조할 수 있다고하여 다형적 참조라함

(6-3) 다형적 참조와 인스턴스 실행

  • poly.parentMethod()를 호출하면 먼저 참조값을 사용하여 인스턴스를 찾고 인스턴스 안에서 실행할 타입도 찾음
  • poly는 Parent 타입이므로 Parent 클래스부터 시작하여 필요한 기능을 찾고 Parent 클래스에 parentMethod()가 있으므로 해당 메서드가 호출됨

(6-4) 다형적 참조의 한계

  • Parent poly = new Child()로 자식을 참조한 상황에서 poly가 자식 타입인 Child에 있는 childMethod()를 호출할 수 없음
  • poly.childMethod()를 실행하면 먼저 참조값을 통해 인스턴스를 찾고 인스턴스안에서 실행할 타입을 찾는데 poly는 Parent 타입이므로 Parent 클래스 부터 시작해서 필요한 기능을 찾음
  • 그러나 상속 관계는 부모 방향으로 찾아 올라갈 수는 있지만 부모는 자식의 정보가 하나도 없으므로 자식 방향으로 찾아 내려갈 수는 없음
  • Parent는 부모 타입이고 상위에는 부모가 없으므로(실제로는 최고조상인 Object가 있음) childMethod()를 찾을 수 없기 때문에 컴파일 오류가 발생함
  • 이런 경우 childMethod()를 호출하고 싶다면 캐스팅을 통해 해결할 수 있음
  • 다형적 참조의 핵심은 부모는 자식을 품을 수 있다라는 것!
  • 그런데 자식 타입의 변수로 접근하면 부모의 기능을 모두 사용할 수 있는데 굳이 불편하기만 해보이는 부모타입의 변수로 자식 타입을 참조하는 다형적 참조가 왜 필요한가 의문이 들 수 있는데, 이부분은 다형성의 다른 이론들도 함께 알아야 이해할 수 있음

2. 다형성과 캐스팅

1) 다형성과 캐스팅

(1) CastingMain1

    • Parent poly = new Child()와 같이 부모 타입의 변수를 사용하게 되면 poly.childMethod()와같이 자식 타입에 있는 기능은 호출할 수 없음
    • 그러나 다운 캐스팅을 활용한 덕분에 부모 타입 변수를 자식 타입으로 임시로 캐스팅후 Child 타입의 변수에 대입하여 해당 변수를 활용하여 Child클래스에 있는 childMethod()를 호출할 수 있게됨
package poly.basic;

public class CastingMain1 {
    public static void main(String[] args) {
        // 부모 변수가 자식 인스턴스 참조, 다형적 참조
        Parent poly = new Child();
        // 단, 자식의 기능은 호출할 수 없음. 컴파일 오류 발생
//        poly.childMethod();

        // 다운캐스팅, 부모 타입 -> 자식 타입
        Child child = (Child) poly;
        child.childMethod();
    }
}
/* 실행 결과
Child.childMethod
*/

 

(2) 다운캐스팅

좌) 부모 타입 변수로 자식 메서드 호출 불가 / 우) 다운캐스팅으로 자식 메서드를 호출

  • 상속관계는 부모로만 찾아서 올라갈 수 있으므로 이미 부모타입인 poly변수는 자식 타입의 메서드인 childMethod()를 호출할 수 없고 컴파일 오류가 발생함
  • 이럴때는 호출하는 타입을 자식인 Child 타입으로 변경하면 인스턴스의 Child에 있는 childMethod()를 호출할 수 있음
  • 그러나 Child child = poly 처럼 부모타입을 사용하는 변수를 자식 타입에 대입하려고 하면 컴파일 오류가 발생하는데 이때는 다운캐스팅이라는 기능을 사용하여 부모 타입을 잠깐 자식 타입으로 변경하면 됨
  • Child child = (Child) poly 처럼 ()에 타입을 지정하면 참조 대상을 특정 타입으로 변경할 수 있으며 이렇게 특정 타입으로 변경하는 것을 캐스팅이라함
  • 캐스팅을 하게 되면 poly는 Parent 타입이므로 이 타입을 (Child)를 사용하여 일시적으로 자식 타입인 Child 타입으로 변경한 후 Child child에 대입함
  • 즉, 캐스팅을 한다고 해서 Parent poly의 타입이 변하는 것이 아니라 해당 참조값을 꺼내고 꺼낸 참조값이 Child 타입이 되는 것이므로 poly의 타입은 기존과 같이 Parent임
// 캐스팅 실행 순서
Child child = (Child) poly  //다운캐스팅을 통해 부모타입을 자식 타입으로 변환한 다음에 대입 시도
Child child = (Child) x001  //참조값을 읽은 다음 자식 타입으로 지정
Child child = x001          //최종 결과

 

(3) 캐스팅 용어

  • 업캐스팅(upcasting): 부모 타입으로 변경
  • 다운캐스팅(downcasting): 자식 타입으로 변경

3. 캐스팅의 종류

1) 일시적 다운 캐스팅

(1-1) CastingMain2

  • 자식 타입의 기능을 사용하려면 다운캐스팅 결과를 변수에 담아두고 이후에 기능을 사용하면 되는데 변수에 담아두는 과정이 번거로움
  • 이런 과정 없이 일시적으로 다운캐스팅을 해서 인스턴스에 있는 하위 클래스의 기능을 바로 호출할 수 있으며 실행해보면 정상적으로 childMethod()가 호출되는 것을 확인할 수 있음
package poly.basic;

public class CastingMain2 {
    public static void main(String[] args) {
    
        Parent poly = new Child();

        // 일시적 다운캐스팅 - 해당 메서드를 호출하는 순간만 다운캐스팅
        ((Child) poly).childMethod();
    }
}

 

(1-2) 그림으로 설명

  • Parent 타입인 poly를 임시로 Child로 변경하고 메서드를 호출할 때 Child 타입에서 찾아서 실행함
  • 자바에서는 무조건 값을 복사하여 대입하므로 poly가 Child 타입으로 바뀌는 것이 아니라 해당 참조값을 꺼내고 꺼낸 참조값이 Child 타입이 되는 것임

2) 업캐스팅

(1) CastingMain3

  • 현재 타입을 부모 타입으로 변경하는 것을 업캐스팅이라고 함
  • Child 타입인 child변수를 Parent 타입에 대입하기 위해 캐스팅을 해보면 캐스팅 코드인 (타입)을 생략해도 정상적으로 동작함
  • 업캐스팅은 생략할 수 있지만 다운 캐스팅은 생략할 수 없으며, 업캐스팅은 매우 자주 사용하기 때문에 생략하는 것을 권장함
  • 자바에서 부모는 자식을 담을 수 있어서 업캐스팅은 생략해도 되고 다운캐스팅은 필요하다면 개발자가 강제로 직접 명시적으로 캐스팅을 해야하는지 궁금증이 생길 수 있는데 바로 아래에서 자세히 설명함
package poly.basic;

public class CastingMain3 {
    public static void main(String[] args) {
        Child child = new Child();
        Parent parent1 = (Parent) child;    // 업캐스팅은 생략이 가능함, 생략을 권장함
        Parent parent2 = child;             // 업캐스팅 생략

        parent1.parentMethod();
        parent2.parentMethod();
    }
}

4. 다운캐스팅과 주의점

1) 다운 캐스팅과 주의점

(1) CastingMain4

  • 결과적으로 다운캐스팅은 잘못하면 심각한 런타임 오류가 발생할 수 있음
  • 실행 결과를 보면 child1.childMethod()는 잘 호출되었지만 child2.childMethod()는 실행되지 못하고 오류가 발생함
package poly.basic;

// 다운캐스팅을 자동으로 하지 않는 이유
public class CastingMain4 {
    public static void main(String[] args) {
        
        Parent parent1 = new Child();   // Parent 타입으로 Child 인스턴스 생성
        Child child1 = (Child) parent1;
        child1.childMethod();   // 문제 없음

        Parent parent2 = new Parent();  // Parent 타입으로 Parent 인스턴스 생성
        Child child2 = (Child) parent2; // 런타임 오류 - ClassCastException
        child2.childMethod();   // 실행 불가
    }
}
/* 실행 결과
Child.childMethod
Exception in thread "main" java.lang.ClassCastException:
class poly.basic.Parent cannot be cast to class poly.basic.Child (poly.basic.Parent and poly.basic.Child are in unnamed module of loader 'app')
	at poly.basic.CastingMain4.main(CastingMain4.java:11)

*/

 

(2) 다운캐스팅이 가능한 경우와 불가능한 경우

좌) 다운캐스팅이 가능한 경우 / 우) 다운캐스팅이 불가능한 경우

  • 예제의 parent1의 경우 다운캐스팅을 해도 문제가 없음
  • 예제의 parent2를 다운캐스팅을 하면 ClassCastException이라는 심각한 런타임 오류가 발생하는데 new Parent()로 부모 타입으로 객체를 생성했기 때문에 메모리 상에 자식 타입은 전혀 존재하지 않음
  • 인스턴스 생성 결과를 parent2에 담아두는데 까지는 문제가 발생하지 않지만 parent2를 Child 타입으로 다운캐스팅을하면 메모리 상에 Child 자체가 존재하지 않아 Child 자체를 사용할 수 없음
  • 자바에서는 이렇게 사용할 수 없는 타입으로 다운캐스팅하는 경우에 ClassCastException이라는 예외를 발생시킴
  • 예외가 발생하면 다음 동작이 실행되지 않고 프로그램이 종료되어 child2.childMethod() 코드 자체가 실행되지 않음

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

(1) 업캐스팅은 이런 문제가 절대로 발생하지 않음

  • 업캐스팅은 이런 문제가 절대 발생할 수 없는데 이유는 객체를 생성하면 해당 타입의 상위 부모 타입은 모두 함께 생성되기 때문임
  • 따라서 위로만 타입을 변경하는 업캐스팅은 메모리 상에 인스턴스가 모두 존재하므로 항상 안전하며 캐스팅도 생략할 수 있는 것임
  • 반면 다운캐스팅의 경우에는 객체 생성시 해당 타입의 상위 부모 타입은 모두 함께 생성되지만 자식 타입은 생성되지 않기 때문에 인스턴스에 존재하지 않는 하위 타입으로 캐스팅하는 문제가 발생할 수 있게 됨
  • 즉 개발자가 이런 문제를 인지하고 사용해야 한다는 의미로 명시적 캐스팅을 해주어야 함

(2) 업캐스팅 - 그림 설명

 

  • 클래스 A, B, C는 상속관계이므로 new C()로 인스턴스를 생성하면 인스턴스 내부에 자신과 부모인 A, B, C가 모두 생성됨
  • 따라서 C의 부모 타입인 A, B, C 모두 C 인스턴스를 참조할 수 있으며 상위로 올라가는 업캐스팅은 인스턴스 내부에 부모가 모두 생성되기 때문에 문제가 발생하지 않음
  • A a = new C(): A로 업캐스팅
  • B b = new C(): B로 업캐스팅
  • C c = new C(): 자신과 같은 타입

(3) 다운캐스팅 - 그림 설명

  • new B()로 인스턴스를 생성하면 인스턴스 내부에 자신과 부모인 A, B가 생성이 되므로 A, B모두 B 인스턴스를 참조할 수 있음
  • 객체를 생성할 때 하위 자식은 생성되지 않기 때문에 하위로 내려가는 다운캐스팅은 인스턴스 내부에 없는 부분을 선택하는 문제가 발생할 수 있음
  • C c = new B(): 하위 타입은 대입할 수 없으므로 컴파일 오류 발생
  • C c = new (C) new B(): 하위 타입으로 강제 다운캐스팅을하면 B인스턴스에 C와 관련된 부분이 없으므로 잘못된 캐스팅이 되어 ClassCastException 런타임 오류가 발생함

(4) 컴파일 오류 vs 런타임 오류

  • 컴파일 오류는 변수명 오타, 잘못된 클래스 이름 사용 등 자바 프로그램을 실행하기 전에 발생하는 오류인데 이런 오류는 IDE에서 즉시 확인할 수 있기 때문에 안전하고 좋은 오류임
  • 반면, 런타임 오류는 프로그램이 실행되고 있는 시점에 발생하는 오류인데 IDE에서는 오류가 보이지 않고 보통 고객이 해당 프로그램을 실행하는 도중에 발생하기 때문에 매우 안좋은 오류임

5. instanceof

1) instanceof

(1) CastingMain5

  • 다형성에서 참조형 변수는 다양한 자식을 대상으로 참조할 수 있으므로 변수가 참조하는 인스턴스의 타입을 확인하고 싶다면instanceof키워드를 사용하면됨
  • call(Parent parent) 메서드는 매개변수로 넘어온 parent가 참조하는 타입에 따라서 다른 명령을 수행하는데, 기본적으로는 parentMethod()를 호출하고 parent의 인스턴스 타입이 Child면 다운캐스팅을 하고childMethod()도 호출하도록 작성되었음
  • 출력해보면 parent2는 Child인스턴스이므로 parentMethod()메소드 호출에 이어서 childMethod()메소드도 호출되는 것을 확인할 수 있음
  • 지금처럼 다운캐스팅을 수행하기 전에는 먼저 instanceof를 사용하여 원하는 타입으로 변경이 가능한지 확인한 다음에 다운캐스팅을 수행하는 것이 안전함
package poly.basic;

public class CastingMain5 {
    public static void main(String[] args) {
        Parent parent1 = new Parent();
        System.out.println("parent1 호출");
        call(parent1);

        Parent parent2 = new Child();
        System.out.println("parent2 호출");
        call(parent2);
    }

    private static void call(Parent parent) {
        parent.parentMethod();

        if (parent instanceof Child) {
            System.out.println("Child 인스턴스 맞음");
            Child child = (Child) parent;
            child.childMethod();
        }
    }
}
/* 실행 결과
parent1 호출
Parent.parentMethod
parent2 호출
Parent.parentMethod
Child 인스턴스 맞음
Child.childMethod
*/

 

(2) instanceof 동작

  • call()메서드가 처음 호출할 때 parent는 Parent의 인스턴스를 참조하므로 instanceof의 결과가 false를 반환함
  • 다음 call()메서드가 호출할 때 parent는 Child의 인스턴스를 참조하므로 instanceof의 결과는 true를 반환하고 나머지 로직을 수행하게 됨
  • 참고로 instanceof 키워드는 오른쪽 대상의 자식 타입을 왼쪽에서 참조하는 경우에도 true를 반환하는데 쉽게 이야기해서 오른쪽에 있는 타입에 왼쪽에 있는 인스턴스 타입이 들어갈 수 있는지 대입해보고 대입이 가능하면 true, 불가능하면 false가 됨
new Parent() instanceof Parent
Parent p = new Parent() //같은 타입 true

new Child() instanceof Parent
Parent p = new Child() //부모는 자식을 담을 수 있음 true

new Parent() instanceof Child
Child c = new Parent() //자식은 부모를 담을 수 없음 false

new Child() instanceof Child
Child c = new Child() //같은 타입 true

 

(3) 자바 16 - Pattern Matching for instanceof

  • 자바 16부터는 instanceof를 사용하면서 동시에 변수를 선언할 수 있음
  • 현재는 실무에서 자주 사용하진 않는데 점차 많이 사용할 것으로 보임
package poly.basic;

public class CastingMain6 {
    public static void main(String[] args) {
        // ... 기존 코드 동일 생략
    }

    private static void call(Parent parent) {
        parent.parentMethod();
        
        // Child 인스턴스인 경우 childMethod() 실행
        if (parent instanceof Child child) {
            System.out.println("Child 인스턴스 맞음");
            child.childMethod();
        }
    }
}

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

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

(1) 설명

  • 다형성을 이루는 또 하나의 중요한 핵심 이론은 메서드 오버라이딩인데 꼭 기억해야할 점은 오버라이딩 된 메서드가 항상 우선권을 가진다는 점임
  • 그래서 이름도 기존 기능을 덮어 새로운 기능을 재정의 한다는 뜻의 오버라이딩임
  • 메서드 오버라이딩의 진짜 힘은 다형성과 함께 사용할 때 나타남

(2) UML 설명

  • Parent, Child 모두 value라는 멤버 변수를 가지고 있음
  • Parent, Child 모두 method()라는 같은 메서드를 가지고 있으며 Child에서 오버라이딩하였음
  • 멤버 변수는 오버라이딩 되지 않으며 메서드는 오버라이딩이 됨

(3) Parent

package poly.overriding;

public class Parent {

    public String value = "Parent";

    public void method() {
        System.out.println("Parent.method");
    }
}

 

(4) Child

  • Parent를 상속받고 method()를 오버라이딩
package poly.overriding;

public class Child extends Parent {

    public String value = "child";

    @Override
    public void method() {
        System.out.println("Child.method");
    }
}

 

(5) OverridingMain

  • 본인 타입 변수가 본인 인스턴스를 참조하는 부분은 계속 반복 학습했으므로 별다른것은 없음
  • 그러나 부모 변수가 자식 인스턴스를 참조하는 다형적 참조인 경우 poly.method()를 보면 Parent 타입 변수로 method()를 호출했음에도 오버라이딩된 Child의 method()가 호출된 것을 확인할 수 있음
package poly.overriding;

public class OverridingMain {
    public static void main(String[] args) {
        // 자식 변수가 자식 인스턴스 참조
        Child child = new Child();
        System.out.println("Child -> Child");
        System.out.println("value = " + child.value);
        child.method();

        // 부모 변수가 부모 인스턴스 참조
        Parent parent = new Parent();
        System.out.println("Parent -> Parent");
        System.out.println("value = " + parent.value);
        parent.method();

        // 부모 변수가 자식 인스턴스 참조(다형적 참조)
        Parent poly = new Child();
        System.out.println("Parent -> Child");
        System.out.println("value = " + poly.value);    // 변수는 오버라이딩 X
        poly.method();  // 메서드 오버라이딩
    }
}
/* 실행 결과
Child -> Child
value = child
Child.method
Parent -> Parent
value = Parent
Parent.method
Parent -> Child
value = Parent
Child.method
*/

 

(6) 본인 타입의 변수가 본인 인스턴스를 참조

좌) Child -> Child / 우) Parent -> Parent

  • child 변수는 Child 타입이므로 child.value, child.method()를 호출하면 인스턴스의 Child 타입에서 기능을 찾아서 실행함
  • 마찬가지로 parent 변수는 Parent 이므로 parent.value, parent.method()를 호출하면 인스턴스의 Parent 타입에서 기능을 찾아서 실행함

(7) Parent -> Child, 다형적 참조 - 중요**

  • poly 변수는 Parent 타입이므로 poly.value, poly.method()를 호출하면 인스턴스의 Parent 타입에서 기능을 찾아서 실행함
  • poly.value: Parent 타입에 있는 value 값을 읽음
  • poly.method(): Parent 타입에 있는 method()를 실행하려고하는데 하위 타입인 Child.method()가 오버라이딩 되어있으므로 Parent.method()가 아닌 Child.method()가 실행됨
  • 오버라이딩 된 메서드는 항상 우선권을 가짐
  • 즉, 자식에서도 오버라이딩하고 손자에서도 같은 메서드를 오버라이딩하면 손자의 오버라이딩 메서드가 우선권을 가지며 가장 하위 자식의 오버라이딩 된 메서드가 우선권을 가짐

(8) 정리

  • 다형성을 이루는 핵심 이론은 다형적 참조와 메서드 오버라이딩이며 이 둘을 이해하고 나면 진정한 다형성의 위력을 알 수 있게됨
  • 다형적 참조: 하나의 변수 타입으로 다양한 자식 인스턴스를 참조할 수 있는 기능
  • 메서드 오버라이딩: 기존 기능을 하위 타입에서 새로운 기능으로 재정의하고 오버라이딩 된 메서드는 항상 우선권을 가짐