본문 바로가기

전공

자바 업캐스팅 다운캐스팅 (문제풀이)

반응형

프로그래밍 언어론을 공부하면 피해갈 수 없는 자바의 업캐스팅과 다운캐스팅.. 볼 때마다 헷갈리기에 한번은 정리하고 갈 필요가 있다고 생각했었다. 

업캐스팅과 다운캐스팅의 문제는 보통 상속의 관계에서 일어난다. 이를 이해하기 위해서 딱 두가지 정도만 개념을 확실하게 잡고가자.

  1. 참조변수와 인스턴스의 구분을 확실하게 하자
  2. 큰 것은 작은 것을 담지 못한다.

 

 

  1. 대입(=)이 일어날 때, 참조변수와 인스턴스끼리 대입이 일어나는 경우 ( a = new A())
  2. 대입(=)이 일어날 때, 참조변수와 참조변수끼리 대입이 일어나는 경우 ( a = b )

 

  1. 부모가 자식을 참조하려 할 때
  2. 자식이 부모를 참조하려 할 때

 

이것이 무슨 뜻인지 예시를 들면서 설명하겠다.  아래와 클래스가 있다. Child 클래스는 Parent 클래스를 상속하고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Parent {
    int parentVar;
    
    void parentMethod{}{
        ...
    }    
}
 
class Child extends Parent{
    int childVar;
 
    void childMethod(){
        ...
    }
}
cs

 

  1. 대입(=)이 일어날 때, 참조변수와 인스턴스끼리 대입이 일어나는 경우 ( a = new A())
1
2
3
4
5
6
7
8
9
10
class Example{
 
    public static void main(String[] args){
 
        Parent p = new Child(); // (1). 문제없음
       Child  c = new Parent(); // (2). Compile Error
 
    }
 
}
cs

 

이 경우, 객체가 생성된다면 아래와 같이 생성될 것이다. 

 

이때, '큰 것은 작은 것을 담지 못한다'를 기억해보자

(1). 작은 것(Parent p)는 큰 것(Child)를 담지 못한다. // 오류없이 진행 가능

(2). 큰 것(Child c)는 작은 것(Parent)를 담지 못한다. // 컴파일 오류 발생

 

그 이유는, 참조 때문이다. Child는 Parent 객체보다 더 많은 멤버를 가지고 있다. (childVar, childMethod)

따라서, Child 라고 알고있는 변수 c가 c.childMethod()를 접근하려고 한다. (변수 c는 이 정보를 알고 있음)

그러나, 실제 변수 c가 참조하고 있는 객체는 Parent 객체다. Parent 객체엔 childMethod()가 존재하지 않는다.

 

따라서, 잘못 참조하는 것을 미리 방지하기 위해 컴파일에서 큰 것에 작은 것을 담는 것을 못하게 막는다.

 

정리하자면, new 연산자를 이용하여 변수에 객체를 직접 생성할 때는 아래와 같이 생각하면 된다.

1. Parent p = new Child() ==> 가능

2. Child c = new Parent() ==> 불가능

 

그러면 메서드를 호출하는 과정은 어떻게 될까?

1
2
3
4
5
6
7
8
9
10
11
12
13
class Example{
 
    public static void main(String[] args){
 
        Parent p = new Child(); //
        
 
        p.parentMethod(); // Child의 parentMethod()가 호출됨
        p.childMethod();  // 오류 발생함. p는 childMethod의 존재를 모름

System.out.println(p.parentVar) // Parent의 parentVar가 호출됨
 
    }
 
}
cs

왜 p.parentMethod()가 아닌 Child에 있는 parentMethod()가 호출될까?

자바는 메서드를 호출 할 때, 런타임 과정에서 어떤 메서드를 호출할지 정하는 정적 바인딩이다. 따라서, child 클래스에 오버라이딩한 parentMethod()가 존재한다면! 오러라이딩 된 메서드를 호출한다.

변수는 동적바인딩 대상이 아니기 때문에  참조변수(Parent)가 알고 있는 변수가 호출된다.

매우 성가시지만 원리만 이해한다면 외울 필요는 없다.

 

정리하자면

  1. Method는 동적바인딩이 되기 때문에, 자식에서 Overriding 된 메서드가 있다면 그 메서드가 호출된다.
  2. 변수는 동적바인딩이 아니기 때문에, 자식에서 Overriding 되었다고 해도 참조변수가 알고 있는 변수가 호출된다.

 

아래 케이스를 보자. 아래 케이스는 조금 더 복잡하다.

2. 대입(=)이 일어날 때, 참조변수와 참조변수끼리 대입이 일어나는 경우 ( a = b )

1
2
3
4
5
6
7
8
9
10
11
12
13
class Example{
    public static void main(String[] args){
        Parent p = null;          // (1)
       Child c1 = new Child();  // (2)
       Child c2 = null;            // (3)
 
        p = c1;                    // (4)
        p.childMethod();            // (5) Runtime Error
 
        c2 = (Child)p;             // (6)
        c2.childMethod();          // (7)
    }
}
cs
  1. Parent 구조를 아는 참조변수 p가 선언되었다.
  2. Child 구조를 아는 참조변수 c1가 선언되었고, Child 인스턴스가 생성되었다.
  3. Child 구조를 아는 참조변수 c2가 선언되었다.
  4. Parent 구조를 아는 참조변수 p에 Child 인스턴스가 대입되었다. 이때 형변환이 일어나지만, 묵시적 형변환이 가능하다. 실질적인 명령어는 p = (Parent) c1; 이다. c1을 Parent 로 형변환을 시킨 다음 대입하는 것이다.
  5. 런타임 오류가 발생한다. Parent 구조를 알고 있는 p는 childMethod()란 메서드를 모른다. 
  6. Child 구조를 아는 참조변수 c2에 Child 인스턴스가 대입되었다. 이때는 묵시적 형변환이 안되기때문에 명시적으로 (Child)를 사용해주어야 한다.
  7. Child 구조를 아는 c2는 childMethod()란 메서드를 알고있기에 문제없이 수행된다.

 

형변환은 큰 것을 작은 것에 대입할 때는 안해도 되지만, 작은 것을 큰 것에 대입할 때는 해야 한다.

- p = c 에서는 묵시적으로 변환된다.

- c = p 는 묵시적 변환이 안된다. 따라서 (Child)를 통해 형변환해주어야 한다.

 

 

 

낵아 봐도 무슨소리인지 이해하기가 조금 버겁다.

나중에 조금 다시 글을 다듬어야 겠다 . ..  . 지금말고 . . . .

안녕 . ..

반응형