- 여러개의 형태를 갖는다는 의미
- 한 객체가 여러 타입의 객체로 취급될 수 있는 능력
- 하나의 행동으로 여러가지 일을 수행
- 상속을 이용해 부모 타입으로부터 파생된 여러 가지 타입의 자식 객체를 부모 클래스 타입 하나로 다룰 수 있는 기술
✅ 자바에서 부모타입은 자식타입
및 그 하위타입
들도 담을 수 있다
다형적 참조
: 하나의 변수 타입으로 다양한 자식 인스턴스를 참조할 수 있는 기능Parent p = new Child(); // 부모는 자식을 담을 수 있음 (마음이 넓기때문에)
Parent p2 = new Grandson() // 호출 가능 (Child 클래스의 자식클래스 => GrandSon)
// Child c = new Parent(): // 반대로 자식은 부모를 담을 수 없음 (컴파일 오류 발생)
p.parentMethod() // 호출 가능
// p.childMethod() // 자식의 기능은 호출 불가능 (컴파일 오류)
위와같이 childMethod
를 호출하고 싶을 때는?
-> 캐스팅
이 필요하다
- 자식 클래스의 객체가 부모 클래스 타입으로 형변환 되는 것
- 상속 관계에 있는 부모, 자식 클래스 간에 부모타입의 참조형 변수가 모든 자식 타입의 객체 주소를 받을 수 있음
- 부모 타입으로 변경
//ex
Sonata s = new Sonata(); // Sonata 클래스는 Car 클래스의 자식
// 부모는 자식을 담을 수 있음
Car c = (Car)s; // Sonata 클래스형에서 Car클래스 형으로 업캐스팅되어 바뀜
Car c2 = s; // 업캐스팅이 생략된거임 (위 코드와 동일한 결과)
// 이 경우 c는 원래 부모타입이 였던 멤버만 참조 가능! (자손클래스가 선언된 메소드, 변수)를 읽을 수 ❌
Sonata s = new Car(); // 반대로는 불가능 (이유 : 자식클래스 안에 부모클래스의 것을 저장할 수 없기 때문)
- 업캐스팅과 반대로 자식 객체의 주소를 받은 부모 참조형 변수를 가지고 자식의 멤버를
참조해야 할 경우, 부모 클래스 타입의 참조형 변수를 자식 클래스 타입으로 형 변환하는 것- 부모타입에서 자식타입으로 다운시키는것 = 자식 타입으로 변경
- 자동으로 처리되지 않기 때문에 반드시 후손 타입 명시해서 형 변환
// ex
Car c = new Sonata(); // c -> 부모 참조형 변수 c는 자식 객체의 주소를 받음
((Sonata)c).moveSonata(); // movSonata()는 자식의 멤버메소드
// c는 부모참조형 변수라서 자식 메소드에 접근하지 못하므로 자식 클래스 타입으로 형변환하여 접근함!
Car c = new Car();
Sonata s = new Sonata(); // s -> 자식 참조형 변수
s = (Sonata)c; // 이 경우 양쪽 타입이 맞아야 하므로, 부모클래스 c에 자식클래스로 형변환해야함
s.moveSonata();
// 일시적 다운캐스팅 - 해당 메서드를 호출하는 순간만 다운캐스팅
// (Sonata)c.moveSonata(); // 컴파일오류 (연산자 우선순위로 인해 .moveSonata() 가 먼저 실행됨)
// 즉 위 우선순위를 괄호로 해결
((Sonata)c).moveSonate(); // (O) -> Car이 Sonata 타입으로 바뀌는것은 아님
//다운캐스팅을 자동으로 하지 않는 이유
public class CastingMain4 {
public static void main(String[] args) {
Parent parent1 = new Child();
Child child1 = (Child) parent1;
child1.childMethod(); //문제 없음
Parent parent2 = new Parent(); // 객체를 생성하면 하위 자식은 생성하지 않음
Child child2 = (Child) parent2; //런타임 오류 - ClassCastException
child2.childMethod(); //실행 불가
}
}
parent1
-> parent1은 생성될 때, Child도 인스턴스에 남아 있기 때문에 다운 캐스팅을 해도 문제 x
-> Parent parent1 = new Child();
객체 생성시 메모리 상에 자식 타입이 존재
parent2
-> parent2는 생성될 때, Child가 인스턴스에 없기 때문에 다운캐스팅을 하면 문제 발생
-> Parent parent2 = new Parent();
객체 생성시 메모리 상에 자식 타입이 없음
ClassCastException
: 사용할 수 없는 타입으로 다운캐스팅 하는 경우 발생
📌 업캐스팅은 안전한 이유?
ex) A
> B
> C
관계라고 했을 때 C
를 생성하면
인스턴스 내부에 자신과 부모인 A,B,C 모두 생성되기 때문에 문제가 발생하지 않음
즉 메모리에 부모까지 생성하기 때문에, 부모타입으로 참조하는 것은 문제가 없음
부모클래스 = 자식클래스 일 경우 [자동으로 자식클래스가 업캐스팅 되서 부모 클래스가됨]
자식클래스 = 부모클래스 일 경우 [강제로 부모클래스를 자식클래스로 형변환 하지 않으면 오류]
어떤 클래스 형의 객체 주소를 참조
하고 있는지 확인 할때 사용하는 연산자//ex
public void testPerson() {
Person p; // p라는 객체로 3개를 다 접근가능함
p = new Student("유병승",19,1,"실용음악");
printPerson(p);
printPerson(new Student("최주영",26,2,"컴공")); // if문으로 감
printPerson(new Teacher("유병승",19,"web",100)); // else if 문으로감
// printPerson(new PolyTest()); // Person을 상속받고 있는 것들만 인수로 넣을 수 있음
}
public void printPerson(Person p) { // p를 인수로넣었을 때 Person을 상속받은 것들을 접근가능
// instanceof
if(p instanceof Student) {
System.out.println(p.getName()+" "+p.getAge()
+" "+ ((Student)p).getGrade() // grade와 major은 자식 필드이기때문에
+" "+ ((Student)p).getMajor()); // 자식클래스로 형변환해서 불러야함
}else if(p instanceof Teacher) {
System.out.println(p.getName()+" "+p.getAge()
+" "+ ((Teacher)p).getSubject() // grade와 major은 자식 필드이기때문에
+" "+ ((Teacher)p).getSalary()); // 자식클래스로 형변환해서 불러야함
}
}
예시를 보면서 이해해보자
부모 클래스 : Parent
(멤버변수 : value
) (멤버메소드 : method()
)
자식 클래스 : Child
(멤버변수 : value
) (멤버메소드 : method()
)
부모와 자식의 필드, 메소드는 모두 동일한 상태이다
다음은 위 예시를 코드를 쓴 것이다
// 부모클래스
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 // Parent 클래스의 method() 메소드를 재정의한 것
public void method() {
System.out.println("Child.method");
}
}
// main문
//자식 변수가 자식 인스턴스 참조
Child child = new Child(); // Child -> Child (1)
System.out.println("value = " + child.value);
child.method();
//부모 변수가 부모 인스턴스 참조
Parent parent = new Parent(); // Parent -> Parent (2)
System.out.println("value = " + parent.value);
parent.method();
//부모 변수가 자식 인스턴스 참조(다형적 참조)
Parent poly = new Child(); // Parent -> Child (3)
System.out.println("value = " + poly.value); // 변수는 오버라이딩X
poly.method(); //메서드 오버라이딩
// 실행결과
value = child
Child.method
value = parent
Parent.method
value = parent
Child.method
- (1) 번인 경우
자식 변수가 자식 인스턴스 참조
자식 클래스Child
인스턴스를 생성 -> 부모 인스턴스까지 생성
자식에서 접근한멤버변수
,멤버메소드
가 있는지 확인해보고 없으면 부모에서 찾음
자식에서 다 있기 때문에, 자식꺼 호출
- (2) 번인 경우
부모 변수가 부모 인스턴스 참조
부모 클래스Parent
인스턴스를 생성 -> 부모 인스턴스만 생성 (자식이 있는지 알 수 없음)
부모타입이기 때문에, 부모에서 접근한멤버변수
,멤버메소드
호출
- (3) 번인 경우
부모 변수가 자식 인스턴스 참조(다형적 참조)
부모 클래스Parent
인스턴스 생성 + 자식 클래스Child
인스턴스 생성
이 경우
오버라이딩 된 메소드는 항상 우선권을 가진다 (가장 하위 자식의 오버라이딩 된 메소드가 호출)
필드는 호출한 클래스 것으로 호출된다 (value = Parent)
또 다른 예시
Animal
클래스에 sound 메소드를 만들면
여러 동물 클래스들은 Animal
클래스를 상속받아서
각 동물클래스 마다 오버라이딩 된 sound 메소드 호출하기
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("음매");
}
}
// main
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
Caw caw = new Caw();
soundAnimal(dog); // 멍멍 -> Animal animal = dog
soundAnimal(cat); // 냐옹 -> Animal animal = cat
soundAnimal(caw); // 음매 -> Animal animal = caw
}
//동물이 추가 되어도 변하지 않는 코드
private static void soundAnimal(Animal animal) { // 부모는 자식을 담을 수 있음
System.out.println("동물 소리 테스트 시작");
animal.sound();
System.out.println("동물 소리 테스트 종료");
}
}
메서드 안에서 animal.sound
메서드를 호출하면
오버라이딩 한 메서드가 우선권을 가지기 때문에, 각 동물의 sound
메서드가 호출된다
다형성 덕분에 새로운 동물을 추가해도 코드를 재사용할 수 있다
새로운 기능을 추가할 때, 변하는 부분을 최소화
하는 것이 잘 작성된 코드
📌 문제점
Animal
클래스는 동물이라는 클래스로, 직접 생성해서 사용할 일이 없음Aniaml
클래스를 상속받는 곳에서 sound() 메서드 오버라이딩을 하지 않을 수도 있음제약
이 있는 프로그램이 좋은 프로그램
이러한 문제점은 추상클래스
와 추상메소드
를 통해서 해결 할 수 있다