객체지향 프로그래밍의 대표적인 특징으로는 캡슐화, 상속, 다형성이 있다
그 중에서도 다형성은 객체지향 프로그래밍의 꽃이라고 불린다
프로그래밍에서의 다형성은 한 객체가 여러가지 객체(타입)으로 취급될 수 있음을 뜻한다
하나의 객체는 하나의 타입을 갖지만 다른 타입으로도 사용될 수 있다
이 경우 부모 타입이 생성되기 때문에 자식은 함께 생성되지 않는다
메모리에 부모만 올라간다
자이 경우 자식 타입을 생성했기 때문에 자식과 부모 모두 생성된다
메모리 상에 자식과 부모 모두 올라간다
부모 타입의 변수가 참조하고 있는 자식 타입의 객체에서
메서드 실행 시 부모 타입의 메서드에서 필요한 기능을 찾는다 (호출자의 타입을 통해 대상 타입을 찾는다)
부모 타입의 변수는 자식 타입의 기능을 참조할 수 없다
이런 경우 꼭 자식 타입의 기능이 필요하다면 타입 캐스팅이 필요하다
핵심은 부모 타입이 자식 타입을 담을 수 있다는 것이다
부모 타입의 변수에 담은 다형성 객체를 선언했지만
자식의 기능이 필요할 경우 명시적 형변환을 통해 다운캐스팅을 할 수 있다
기본적으로는 부모 타입의 객체를 자식 타입의 객체에 담는 것은 불가능하지만
일시적으로 자식 타입을 지정해줄 수 있다
Child child = (Child) poly;
이 경우 참조 값의 변수에 자식 타입을 지정하는 것으로
객체 자체의 타입이 변환되는 것은 아니다
변수 내의 값은 참조만 들어있기 때문이다
((Child) poly).childMethod;
// 타입 캐스팅보다 우선순위가 메소드 실행에 있기 때문에 타입 캐스팅에 우선순위를 준다
업캐스팅은 생략을 권장한다 (묵시적 형변환 활용)
Parent parent = (Parent) child
원래라면 이런 코드를 작성해야 하지만 암묵적으로 같은 동작이 일어난다
Parent parent = child
부모 타입으로의 형변환은 자연스러우니 따로 형변환을 해주지 않는 편이 좋다
다운캐스팅을 잘못하면 심각한 런타임 오류가 발생할 수 있다 (ClassCastException)
때문에 다운캐스팅을 자동으로 지원하지 않는다
업캐스팅의 경우 자식 인스턴스 생성 시 부모 타입 또한 함께 메모리에 생성되기 떄문에
항상 안전하다
다운 캐스팅의 경우 부모 인스턴스 생성 시 자식 타입은 함께 생성되지 않는다
다형성에서 참조형 변수는 다양한 자식을 대상으로 참조할 수 있다
어떤 인스턴스를 참조하고 있는지 알아보려면 instanceof 키워드를 사용한다
private static void call(Parent parent) {
if (parent instanceof Child) {
System.out.println("Child 인스턴스");
Child child = (Child) parent;
child.childMethod();
}
}
위 처럼 다운캐스팅 수행 전 instanceof 사용하여 원하는 타입으로 변경 가능한지
확인 후에 다운 캐스팅을 수행하는 것이 안전하다
private static void call(Parent parent) {
if (parent instanceof Child child) { // 이 위치에서 위 코드의 child 변수 선언을 함께 해준다
System.out.println("Child 인스턴스");
child.childMethod();
}
}
다형적 참조, 메서드 오버라이딩
오버라이딩 된 메서드가 항상 우선권을 가진다
메서드 오버라이딩은 다형성과 함께 사용되어 강력한 기능을 제공한다
오버라이딩은 메서드에만 적용된다
오버라이딩 된 메서드를 가지고 있는 상속 관계의 두 인스턴스가 있을 때
다형적 참조를 통해 부모 타입 변수에서 자식 인스턴스를 참조하고 있는 다형성 객체는
필드는 부모의 것을 사용하지만
메서드는 오버라이딩 된 자식의 메서드가 우선 순위를 갖는다
상속 관계에서 생각하면 이러한 동작이 자연스럽다
동물 타입에 강아지를 넣고 오버라이딩 된 sound() 메서드를 호출했다고 생각해보면
당연히 강아지 울음 소리가 나와야 한다
부모 타입으로 부모를 선언하면 부모의 메서드만 존재하기 때문에 부모의 것을 사용
자식 타입으로 자식을 선언하면 오버라이딩 된 자신의 메서드 사용
public static void main(String[] args) {
Animal[] animalArr = {new Dog(), new Cat(), new Cow()};
for (Animal animal : animalArr) {
soundAnimal(animal);
}
private static void soundAnimal(Animal animal) {
animal.sound();
}
}
다형성과 메서드 오버라이딩을 이용하면
위 코드 처럼 형제 타입의 객체들을 하나의 배열을 담아 사용할 수 있다
샷건효과: 하나의 코드 변경 시 함께 변경되어야 할 다른 코드의 범위에 대해 말하는 것
좋은 코드는 샷건효과의 범위가 적은 코드