[김영한_자바기본][10. 다형성1] 상속과 메모리 구조(p.1-23)
1. 자식 객체를 부모 참조 변수에 할당하면 부모 메서드에 접근이 가능하다.
2. 다운캐스팅은 '일시적으로' 부모 타입의 참조 변수를 자식 타입으로 형변환하는 것이다.
3. 다운 캐스팅 수행 전 instanceof로 원하는 타입 변경 확인 후 진행이 안전하다.
4. 주의할점은 멤버 변수는 오버라이딩 되지 않고, 메서드는 오버라이딩 된다는 것이다.
*다형성을 이해하기 위해서는 '다형적 참조'와 '메서드 오버라이딩'에 대한 이해가 선행되어야 한다.
다형성이란 한 객체가 여러 타입의 객체로 취급될 수 있는 능력을 의미한다.
즉 다형성을 사용하면 하나의 객체가 다른 타입을 사용할 수 있다는 뜻이다.
1. 다형적 참조
[p.3 / polyMain_핵심] 자식 객체를 부모 참조 변수에 할당하면 부모 메서드에 접근이 가능하다.
Parent poly = new Child();
- `Parent poly = new Child();`의 경우, `Child` 클래스의 객체를 생성하고 이를 `Parent` 클래스의 참조 변수에 할당
- `Child` 클래스의 인스턴스이지만, 참조 변수의 타입은 `Parent` 클래스이므로 `poly`는 `Parent` 타입으로 간주
- `poly`를 통해 접근할 수 있는 메서드는 해당 참조 변수의 타입인 `Parent` 클래스에서 선언된 메서드들로 한정
- 다형성의 특성 중 하나로, 부모 클래스의 참조 변수로 자식 클래스의 객체를 다룰 수 있다.
[p.2 / Parent]
public void parentMethod() {
System.out.println("Parent.parentMethod");
}
[p.2 / Child] Parent class를 상속받음
public class Child extends Parent{
public void childMethod() {
System.out.println("Child.childMethod");
}
}
[p.3 / 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(); // new child를 통해 Parent객체 생성(상속)
child.childMethod();
child.parentMethod(); //상속받았기 때문에 접근 가능
//부모가 자식에게 접근 가능(부모는 자식을 담을 수 있다)
System.out.println("Parent -> Child");
Parent poly = new Child();
//Child타입의 객체 참조값이 Parent 타입 변수에 담기게 되었다.
poly.parentMethod();
//실제로 child 객체 생성을 통해 -> Parent, Child 객체가 생성되었다.
//그러나 Parent 타입의 변수로 인해 부모 메서드에만 접근 가능하다.
//부모에서 더 높은 상속코드를 찾을 수 없으므로 Parent 메서드만 가능하다
//poly.childMethod();
//반면 자식이 부모를 담을 수 없다. (포용성이 적다)
//Child child1 = new Parent();
}
- `Child -> Child`: `Child` 객체를 생성하고 `childMethod()`와 `parentMethod()`를 호출한다.
> `childMethod()`는 `Child` 클래스에서 정의된 메서드이며, `parentMethod()`는 상속을 통해 `Parent` 클래스에서
상속받은 메서드이다.
- `Parent -> Child`: 다형성을 보여주는 부분이다. `Child` 객체를 `Parent` 타입의 변수에 할당한다.
부모 클래스의 참조 변수로 자식 클래스의 인스턴스(손자까지 가능)를 가리킨다.
2. 다형성과 캐스팅 종류
업캐스팅(upcasting) | 다운캐스팅(downcasting) |
자식 -> 부모 타입으로 변경 | 부모 -> 자식 타입으로 변경 |
컴파일 오류 발생에 주의해야한다. - 자식 & 부모 모두 생성 |
런타임 오류 발생에 주의해야한다. - 자식 or 손자가 누락될 경우 존재 |
1) 다운캐스팅
[p.7 / CastingMain1] 다운캐스팅은 '일시적으로' 부모 타입의 참조 변수를 자식 타입으로 형변환하는 것이다.
public static void main(String[] args) {
Parent poly = new Child();
//객체는 Parent, Child 2개가 만들어지나 실제 타입은 Parent이기 때문
//poly.childMethod(); /부모에서 더 높은 상속은 없기에 접근 불가
//다운캐스팅(부모 타입 -> 자식 타입)
//즉 부모를 자식처럼 일시적으로 바꾼다면 자식엔 접근할 수 있다 = casting 생각
Child child = (Child) poly;
//상기는 Parent와 Child의 객체가 담긴 참조값 x001
//x001은 Parent타입이지만 -> 임시로 x001을 Child로 변환시킴
//즉 참조값 자체만 임시적으로 타입이 바뀌는 것
child.childMethod();
}
- 기존 Casting 구조와 비슷하다.
- 또한 Parent의 poly타입이 변하는 것이 아니라,참조값이 변하는 것이다.
- 즉 호출 타입을 부모에서 자식으로 낮추어(다운 캐스팅 하여) 메서드에 접근할 수 있도록 한다.
[p. 9 / CastingMain2] 일시적 다운 캐스팅_메서드 호출 순간만 다운 캐스팅
public static void main(String[] args) {
Parent poly = new Child();
((Child)poly).childMethod();
}
- poly.childMethod 접근 불가
- poly자체에 Child를 통한 다운캐스팅을 이루고, 묶어준다.
- Parent의 poly타입이 변하는 것이 아니라,참조값이 변하는 것이다.
다운캐스팅은 변수의 타입을 변경하는 것이 아니라, 변수가 참조하는 객체의 타입을 (다운)바꾸는 것이다.
즉 다운캐스팅이 시점만 변수가 참조하는 객체의 타입이 변경되고, 해당 객체의 실제 타입은 변하지 않는다.
[p.7 / CastingMain1_발췌] 일시적 다운 캐스팅_메서드 호출 순간만 다운캐스팅
Parent poly = new Child();
- `Child` 클래스의 객체를 생성하여 `poly`라는 `Parent` 타입의 변수에 할당한다.
- 이때 `poly`는 `Parent` 클래스를 기반으로 선언되었기 때문에 처음에는 부모 클래스의 멤버에만 접근할 수 있다.
[p.7 / CastingMain1_발췌] DownCasting
Child child = (Child) poly;
- 다운캐스팅을 통해 `poly`가 가리키는 객체를 `Child` 타입으로 형변환하면,
새로운 변수에는 그 참조값이 `Child` 타입으로 저장된다.
- 이 과정에서 변수 `poly`의 타입은 변하지 않으며, 단지 형변환된 참조값이 새로운 변수에 할당된다.
- 따라서 `poly`가 `Child` 타입이 되는 것이 아니라, `child` 변수가 `poly`가 가리키는 객체의 타입을 `Child`로 변환하여
새로운 변수에 할당하는 것이다.
(해당 참조값을 꺼내어 새로운 변수에 할당할 때 그 참조값이 `Child` 타입으로 형변환되는 것)
[p.12 / 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(); //실행 불가
}
[p.7 / CastingMain2_발췌] 일시적 다운 캐스팅
Parent parent1 = new Child();
Child child1 = (Child) parent1;
[p.7 / CastingMain2_발췌] 다운 캐스팅 오류(Child 객체를 찾을 수 없음) // ClassCastException
Parent parent2 = new Parent();
Child child2 = (Child) parent2;
- Child 객체가 없다.
- 즉 Child 자체가 메모리에 존재하지 않으므로 이용할 수 없기에 런타임 오류가 발생한다.
2) 업캐스팅
[p.11 / CastingMain3]
public static void main(String[] args) {
Child child = new Child();
Parent parent1 = child; //업캐스팅_부모는 자식을 담을 수 있따
Parent parent2 = (Parent) child; //생략 권장
parent1.parentMethod();
parent2.parentMethod();
}
3. 인스턴스 타입 확인(instanceof)
[p.16 / CastingMain5] 로직 그려보기_다운 캐스팅 수행 전 instanceof로 원하는 타입 변경 확인 후 진행이 안전하다.
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();
}
}
}
- instance of 전 parent를 이해하기 위해선 기존 객체 타입을 생각하면 된다.
- 즉 오른쪽이 왼쪽을 담을 수 있는지 체킹하는 것이다.
- 오른쪽에 있는 타입에 왼쪽에 있는 인스턴스 타입이 들어갈 수 있는지 대입해보면 된다.
[p.17 / instance of 담을 수 있는 것 생각해보기]
public class ex {
new Parent() instanceof Parent;
Parent p = new Parent; //같은 타입
new Child() instanceof Parent;
Parent p = new Child; //부모가 자식 담을 수 있음 (왼에서 오로 담기는지)
new Parent() instanceof Child;
Child c = new Parent; //자식이 부모를 담을 수 없음
new Child() instanceof Child;
Child c = new Child(); //같은 타입
}
instanceof 패턴 매칭이 자동으로 다운 캐스팅을 수행하여 코드를 간단하고 명확하게 만들어준다.
[p.16 / CastingMain5_발췌] 자반 16_pattern Matching for instance of
private static void call(Parent parent) {
parent.parentMethod();
if (parent instanceof Child child) {
System.out.println("Child 인스턴스 맞음");
child.childMethod();
}
}
- instanceof 연산자와 함께 사용되어 객체의 타입을 확인하고 해당 객체를 캐스팅할 수 있도록 도와준다.
- 즉 확인 결과가 참이라면, `child`라는 새로운 변수에 `parent`를 `Child` 타입으로 캐스팅하여 저장한다.
- 자동으로 다운 캐스팅을 수행한다.
4. 다형성과 메서드 오버라이딩
오버라이딩 메서드는 항상 우선권을 가진다.
주의할점은 멤버 변수는 오버라이딩 되지 않고, 메서드는 오버라이딩 된다는 것이다.
[p.20 / 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); //Parent타입 변수 이에 따라 먼저 Parent 객체쪽으로 접근
poly.method(); //method는 오버라이딩됨에 따라 child 객체로 접근
}
- 오버라이딩 메서드는 항상 우선권을 가진다.
[p.20 / Parent]
public class Parent {
public String value = "parent";
public void method() {
System.out.println("Parent.method");
}
}
[p.20 / Child]
public class Child extends Parent {
public String value = "child";
@Override
public void method() {
System.out.println("Child.method");
}
}