다형성이란 상속 관계에서 조상 클래스의 타입으로
자식 클래스 객체를 레퍼런스 할 수 있는 것이다.
아래 사진과 코드로 이를 이해해보자.

public class PolyTest {
SpiderMan onlyOne = new SpiderMan("피터 파커", false);
SpiderMan sman = onlyOne;
Person person = onlyOne;
Object obj = onlyOne;
// Venom venom = onlyOne;
}
SpiderMan의 부모 객체는 Spider 객체의 주소값을 받을 수 있다.
하지만 SpiderMan의 자식 객체는 Spider 객체의 주소값을 받을 수 없다.
다형성은 다른 타입의 객체를 다루는 배열을 생성할 수 있게 해준다.
아래 코드를 살펴보자.
public void useObjectArray() {
Object[] arr = new Object[4];
arr[0] = 1;
arr[1] = "Hello";
arr[2] = new Person();
arr[3] = new SpiderMan("피터파커", true);
for (int i = 0; i < 4; i++) {
System.out.println(arr[i].getClass());
}
}
출력 결과는 아래와 같다.
class java.lang.Integer
class java.lang.String
class c_inheritance.person.Person
class c_inheritance.person.SpiderMan
즉, Object의 다양한 자식 클래스를 하나의 배열에 저장할 수 있다.
※ int는 기본 자료형이라 Object를 상속받지 않았는데 왜 저장이 가능한가?
int는 Wrapper 클래스인 Integer로 autoboxing 되고, Integer는 Object의 자식 클래스라서 저장이 가능하다.
또한, 다형성은 다양한 매개변수를 받을 수 있도록 한다.
Person person = new SpiderMan(); 로 객체를 정의했다.
그러면 person 객체는 Person 클래스의 멤버 메서드는 사용 가능하지만,
SpiderMan 클래스의 멤버 메서드는 사용할 수 없다.
객체 간 형변환은 아래와 같다.
- 묵시적 형변환 : 자손 타입을 조상 타입으로
SpiderMan spiderMan = new SpiderMan();
Person person = spiderMan;- 명시적 형변환 : 조상 타입을 자손 타입으로
Person person = new Person();
SpiderMan spiderMan = (SpiderMan) Person;
그렇다면 명시적 형변환을 하면 자식 클래스의 멤버 메서드를 사용 가능한 것일까?
아래 코드를 살펴보자.
Person person = new Person();
SpiderMan spiderMan = (SpiderMan) person;
여기서 spiderMan은 SpiderMan 클래스의 멤버 메서드를 사용할 수 없다.
person 객체가 생성될 때 SpiderMan 클래스를 위한 메모리 공간은 할당하지 않았기 때문이다.
그래서 애초에 메모리 공간이 할당된 경우에만 자식 클래스의 멤버 메서드에 접근할 수 있다.
아래 코드를 살펴보자.
SpiderMan sMan = new SpiderMan();
Person person = sMan;
if (person instanceof SpiderMan) {
SpiderMan spiderMan = (SpiderMan) Person;
spiderMan.jump(); // 이제 SpiderMan 클래스의 멤버 메서드 접근 가능
} else {
System.out.println(obj.getClass().getName());
}
여기서 spiderMan은 SpiderMan 클래스의 멤버 메서드를 사용할 수 있다.
아래 코드로 이를 이해해보자. jump 메서드는 오버라이딩된 메서드이다.
public class AppropriateParameter {
public void useJump(Person person) {
person.jump();
}
public static void main(String[] args) {
Person person = new Person();
SpiderMan spiderMan = new SpiderMan("피터 파커", true);
AppropriateParameter ap = new AppropriateParameter();
ap.useJump(person);
ap.useJump(spiderMan);
}
}
실행 결과는 아래와 같다.
ap.useJump(person) : Person class의 jump 메서드를 실행한다.
ap.useJump(spiderMan) : SpiderMan class의 jump 메서드를 실행한다.
이는 아래 사실을 상기시킨다.
ap.useJump(spiderMan)에서 매개변수를 받을 때
Person person = new SpiderMan("피터 파커", true)인 상황임에도
부모 클래스인 Person이 자식 클래스인 SpiderMan에서
오버라이딩된 메서드인 jump 메서드를 사용한다.
아래 코드를 살펴보자.
public class AppropriateParameter {
public void useJump1(Object obj) {
if (obj instanceof Person) {
Person casted = (Person) obj;
casted.jump();
}
}
public void useJump2(Person person) {
person.jump();
}
public void useJump3(SpiderMan spiderMan) {
spiderMan.jump();
}
public static void main(String[] args) {
Object obj = new Object();
Person person = new Person();
SpiderMan sman = new SpiderMan("피터 파커", true);
AppropriateParameter ap = new AppropriateParameter();
// 1번 경우
ap.useJump1(obj);
ap.useJump1(person);
ap.useJump1(sman);
// 2번 경우
ap.useJump2(person);
ap.useJump2(sman);
// 3번 경우
ap.useJump3(sman);
}
}
1번 경우는 모든 클래스의 가장 최상위 객체인 Object로 매개변수를 받는다.
2번 경우는 사용할 클래스 중 가장 최상위 객체인 Person으로 매개변수를 받는다.
3번 경우는 특정 클래스인 SpiderMan으로 매개변수를 받는다.
이때, 2번 경우로 매개변수를 받는게 가장 적절하다.
부모 클래스와 자식 클래스 관계를 정리해보자.

Person p = new SpiderMan()