다형적 참조, 메서드 오버라이딩
Parent poly = new Parent(); (O) // 부모 변수가 부모 인스턴스 참조
Child poly = new Child(); (O) // 자식 변수가 자식 인스턴스 참조
Parent poly = new Child(); (O) // 부모 변수가 자식 인스턴스 참조
Child poly = new Parent(); (X) // 자식 변수가 부모 인스턴스 참조 못함_컴파일 에러

부모 변수가 자식 인스턴스를 참조했을 경우 부모 자신의 메소드는 호출이 가능하지만, 자식의 메소드를 호출을 할 수가 없음. 왜냐면 상위 계층의 메소드는 호출이 가능하지만 하위 계층의 메소드는 호출이 불가능하니까.(당연함!)
근데 왜 이런 다형적 참조를 사용하는 걸까?
그리고 다형적 참조에서 자식의 메소드를 사용하는 방법은 없을까? : 캐스팅하면 됨
public class CastingMain1 {
public static void main(String[] args) {
//부모 변수가 자식 인스턴스 참조(다형적 참조)
Parent poly = new Child();
//poly.childMethod(); //단 자식의 기능은 호출할 수 없다. 컴파일 오류 발생
Child child = (Child) poly; //다운캐스팅(부모 타입 -> 자식 타입):자식메소드 사용가능
child.childMethod();
}
}
명식적으로 자료형을 바꿀 때처럼 해당 타입을 앞에 써주면 캐스팅이 되어 자식 메소드를 사용할 수 있게 된다. 캐스팅을 한다고 해서 원본인 poly의 인스턴스가 child가 되는건 아니다. poly에 있는 참조값을 복사해서 child형으로 바꾸고 대입을 하는거임.
Child child = (Child) poly //다운캐스팅을 통해 부모타입을 자식 타입으로 변환한 다음에 대입 시도 Child child = (Child) x001 //참조값을 읽은 다음 자식 타입으로 지정
Child child = x001 //최종 결과
Child child = new Child();
Parent parent1 = (Parent) child; //업캐스팅은 괄호내용을 생략해서 사용.
// 사실 위에서 다형적 참조라고 했던게 업캐스팅
Parent poly = new Child();
Child child = (Child) poly;
parent instanceof Child
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();
}
}
}
오른쪽에 있는 타입에 왼쪽에 있는 인스턴스 타입이 들어갈 수 있는지 대입해보면 확인 가능. 대입 가능하면 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
if (parent instanceof Child) { System.out.println("Child 인스턴스 맞음"); Child child = (Child) parent; } //이랬던 코드를 아래와 같이 수정할 수 있음. if (parent instanceof Child child) { System.out.println("Child 인스턴스 맞음"); child.childMethod(); }
public class Parent {
public String value = "parent";
public void method() {
System.out.println("Parent.method");
}
}
public class Child extends Parent {
public String value = "child";
@Override
public void method() {
System.out.println("Child.method");
}
//자식 변수가 자식 인스턴스 참조
Child child = new Child();
System.out.println("Child -> Child");
System.out.println("value = " + child.value); //value = child
child.method(); //Child.method
//부모 변수가 부모 인스턴스 참조
Parent parent = new Parent();
System.out.println("Parent -> Parent");
System.out.println("value = " + parent.value); // value = parent
parent.method(); // Parent.method
//부모 변수가 자식 인스턴스 참조(다형적 참조)Parent poly = new Child();
System.out.println("Parent -> Child");
System.out.println("value = " + poly.value); //변수는 오버라이딩X // value = parent
poly.method(); //메서드 오버라이딩! // Child.method

변수는 오버라이딩이 안되고, 메서드는 오버라이딩이 된다.
Parent타입의 poly의 value 변수를 조회하면 Parent자신의 변수를 조회하고,
method를 호출하면 오버라이딩된 자식의 method를 호출하게 된다.
(증손주가 메서드 오버라이딩했으면 증손주 메서드가 호출됨.)
다형적 참조(부모타입의 변수에 자식 인스턴스 참조)를 이용하면(업캐스팅)
1. 자신의 변수,
2. 자신의 (오버라이딩 안된) 메소드,
3. 오버라이딩된 자식의 메소드를 사용할 수 있음.
아니 그래서 대체 이걸 어떻게 쓸 수 있는건데?!
예를 하나 들어보자.
dog, cat, caw와 같은 클래스가 있다고 보자. 각 동물 클래스들은 소리를 낼 수 있는데 다 동물이니까 animal이라는 부모클래스를 두고 상속받을 수 있다.
각 동물 인스턴스들을 만들어서 소리내는 메서드를 호출하려고 한다.
public class Animal {
public void sound() {
System.out.println("동물 울음 소리");
}
}
//--------------------------------------------
public class Dog extends Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
}
//--------------------------------------------
public class Cat extends Animal {
@Override
public void sound() {
System.out.println("냐옹");
}
}
//--------------------------------------------
public class Caw extends Animal{
@Override
public void sound() {
System.out.println("음매");
}
}
//--------------------------------------------
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
Caw caw = new Caw();
System.out.println("동물 소리 테스트 시작");
dog.sound();
System.out.println("동물 소리 테스트 종료");
System.out.println("동물 소리 테스트 시작");
cat.sound();
System.out.println("동물 소리 테스트 종료");
System.out.println("동물 소리 테스트 시작");
caw.sound();
System.out.println("동물 소리 테스트 종료");
}
위와 같은 코드는 중복이 발생하는데 방법은 두 가지로 나뉜다.
1. 중복되는 코드를 메서드로 뽑기
2. 객체들을 배열로 담아서 반복문을 돌리기
1번은 파라미터에 객체 하나를 담아버리면 다른 객체들을 담을 수 없기때문에 각 객체마다 메서드를 만들어야한다. 으으..비효율! 탈락!
private static void soundAnimal(Cat cat) {
System.out.println("동물 소리 테스트 시작");
cat.sound();
System.out.println("동물 소리 테스트 종료");
}
2번은 각 객체의 타입이 다르기때문에 할수가 없다!
그럼 어쩌면 좋은가! 할 때 사용되는게 바로 다형적 참조이다.
부모인 Animal 타입 변수를 만들어 각 객체의 인스턴스를 담게 되면 배열로 만들 수 있다.
그리고 메서드 오버라이딩을 통해 각 클래스에 오버라이딩한 메서드까지 불러오게 된다.
Animal cat = new Cat(); // Cat cat = new Cat(); 로 해도 됨.
Animal caw = new Caw(); // Caw caw = new Caw(); 로 해도 됨.
Animal dog = new Dog(); // Dog dog = new Dog(); 로 해도 됨.
Animal[] animals = { cat, caw, dog };
for (Animal animal : animals) {
animal.sound();
}
새로운 기능이 추가되었을 때 변하는 부분을 최소화 하는 것이 잘 작성된 코드임.
public class AnimalMain {
public static void main(String[] args) {
Animal[] animals = { new Cat(), new Caw(), new Dog()};
for (Animal animal : animals) {
soundAnimal(animal);
}
}
public static void soundAnimal(Animal animal) {
System.out.println("동물 소리 시작");
animal.sound();
System.out.println("동물 소리 끝");
}
}
여전히 존재하고 있는 문제점
1. Animal 클래스를 생성할 수 있음: animal이라는걸 사용할 일이 딱히 없음. 자식들인 개, 고양이, 소와 같은걸 사용.
2. Animal 클래스를 상속받는 곳에서 sound() 메서드 오버라이딩을 하지 않을 가능성.: 메서드 오버라이딩한 줄 알았는데 안했다면?!
=> 이를 해결할 수 있는게 추상클래스와 추상 메서드임.