자바 업캐스팅 다운캐스팅 (문제풀이)
프로그래밍 언어론을 공부하면 피해갈 수 없는 자바의 업캐스팅과 다운캐스팅.. 볼 때마다 헷갈리기에 한번은 정리하고 갈 필요가 있다고 생각했었다.
업캐스팅과 다운캐스팅의 문제는 보통 상속의 관계에서 일어난다. 이를 이해하기 위해서 딱 두가지 정도만 개념을 확실하게 잡고가자.
- 참조변수와 인스턴스의 구분을 확실하게 하자
- 큰 것은 작은 것을 담지 못한다.
- 대입(=)이 일어날 때, 참조변수와 인스턴스끼리 대입이 일어나는 경우 ( a = new A())
- 대입(=)이 일어날 때, 참조변수와 참조변수끼리 대입이 일어나는 경우 ( a = b )
- 부모가 자식을 참조하려 할 때
- 자식이 부모를 참조하려 할 때
이것이 무슨 뜻인지 예시를 들면서 설명하겠다. 아래와 클래스가 있다. 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 |
- 대입(=)이 일어날 때, 참조변수와 인스턴스끼리 대입이 일어나는 경우 ( 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)가 알고 있는 변수가 호출된다.
매우 성가시지만 원리만 이해한다면 외울 필요는 없다.
정리하자면
- Method는 동적바인딩이 되기 때문에, 자식에서 Overriding 된 메서드가 있다면 그 메서드가 호출된다.
- 변수는 동적바인딩이 아니기 때문에, 자식에서 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 |
- Parent 구조를 아는 참조변수 p가 선언되었다.
- Child 구조를 아는 참조변수 c1가 선언되었고, Child 인스턴스가 생성되었다.
- Child 구조를 아는 참조변수 c2가 선언되었다.
- Parent 구조를 아는 참조변수 p에 Child 인스턴스가 대입되었다. 이때 형변환이 일어나지만, 묵시적 형변환이 가능하다. 실질적인 명령어는 p = (Parent) c1; 이다. c1을 Parent 로 형변환을 시킨 다음 대입하는 것이다.
- 런타임 오류가 발생한다. Parent 구조를 알고 있는 p는 childMethod()란 메서드를 모른다.
- Child 구조를 아는 참조변수 c2에 Child 인스턴스가 대입되었다. 이때는 묵시적 형변환이 안되기때문에 명시적으로 (Child)를 사용해주어야 한다.
- Child 구조를 아는 c2는 childMethod()란 메서드를 알고있기에 문제없이 수행된다.
형변환은 큰 것을 작은 것에 대입할 때는 안해도 되지만, 작은 것을 큰 것에 대입할 때는 해야 한다.
- p = c 에서는 묵시적으로 변환된다.
- c = p 는 묵시적 변환이 안된다. 따라서 (Child)를 통해 형변환해주어야 한다.
낵아 봐도 무슨소리인지 이해하기가 조금 버겁다.
나중에 조금 다시 글을 다듬어야 겠다 . .. . 지금말고 . . . .
안녕 . ..