Java/[inflearn] 자바 기본

[김영한_자바기본][10. 다형성1] 상속과 메모리 구조(p.1-23)

줌인. 2024. 2. 10. 16:10
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");
    }
}
728x90