지난번 ploymorphism에 대해 이야기 했을 때 up-casting이라는 용어가 나왔습니다.
Up-casting은 자손 타입을 조상 타입으로 형변환하는 것을 의미합니다. 반대로 down-casting은 조상 타입에서 자손 타입으로의 형변환을 의미합니다.
한 번 도표로 살표보면 이렇게 나타낼 수 있습니다.
(도표 추가)
Class 간의 casting은 서로 상속관계에 있는 클래스 사이에서만 가능합니다.
그렇지만 Up-casting(자손->조상)은 항상 가능하지만 down-casting(조상->자손)은 문제를 일으킬 수 있습니다.
그 이유는 조상 클래스와 자손 클래스가 가지고 있는 인스턴스의 수* 때문입니다.
자손 클래스는 조상 클래스를 상속하였기 때문에 조상 클래스의 인스턴스를 모두 상속받게 되죠? 그리고 경우에 따라 자손 클래스만의 인스턴스를 만들기도 합니다. 그렇다는 것은 언제나 자손 클래스의 인스턴스 수 >= 조상 클래스의 인스턴스 수가 성립하게 됩니다.
한 번 예시를 통해 보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
|
class Parent {
private String name;
public void greeting() {
System.out.println("Hello from Parent");
}
}
class Child extends Parent{
private int age;
public void greeting() {
System.out.println("Hello from Child");
}
}
|
cs |
이러한 경우에서 type-casting(형변환)은 아래와 같이 할 수 있습니다.
1
2
3
4
5
6
7
8
|
public class Main {
public static void main(String[] args) {
//Up-casting
Parent parent = new Child();
//Down-casting
Child childParent = (Child) parent;
}
}
|
cs |
오, up-casting은 알겠는데, down-casting 방법이 좀 특이하죠?
Child child = new Parent();
일 것 같지만 이렇게 하면 ClassCastException 에러가 발생합니다.
Down-casting은 자손 클래스 인스턴스( new Child() )를 참조하고 있는 조상 클래스 참조 변수( parent )만 가능합니다.
그리고 up-casting과는 다르게 앞에 casting 할 클래스를 명시해 주어야 합니다.( (Child) )
한 번 parent와 childParent에서 greeting() method를 사용해보겠습니다.
어떤 결과가 나올까요?
두 참조변수 모두 Child class 인스턴스를 가리키고 있으므로 Child class에 정의되어 있는 greeting() method가 호출되었습니다.
조금은 헷갈리지만 계속 사용하다보면 익숙해질 수 있을 것 같습니다!
Upcasting과 Downcasting의 쓰임
그런데, 이런 upcasting과 downcasting은 왜 사용하는 걸까요? 🤔
결론부터 말하자면 공통된 부모 class를 가진 자손 class instance를 쉽게 관리하고 활용하기 위해서 입니다.
한 번 신발 class를 만들어서 쉽게 설명해 보도록 하겠습니다.
Shoes class를 만들고 NikeShoes, NBShoes, AdidasShoes class 이렇게 3개의 클래스는 모두 Shoes class의 자손으로 만들겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class Shoes {
String brand;
public Shoes(String brand) {
this.brand = brand;
}
}
//Shoes class를 상속하는 자손 class들
class NikeShoes extends Shoes {
public NikeShoes(int price) {
super("Nike",price);
}
}
class NBShoes extends Shoes {
public NBShoes(int price) {
super("New Balance",price);
}
}
class AdidasShoes extends Shoes {
public AdidasShoes(int price) {
super("Adidas",price);
}
}
|
cs |
ABC마트 처럼 신발을 한데 모아둔 곳이 있고 그 곳에서 손님이 신발을 브랜드 별로 신어본다고 가정하겠습니다.
NikeShoes class의 인스턴스를 신어(사용) 볼 수 있게 하는 method를 만들어 볼까요?
1
2
3
4
5
6
7
8
|
class Customer {
//NikeShoes 인스턴스를 신는 method
void tryNikeShoes(NikeShoes nikeShoes) {
System.out.println("Customer is trying " + nikeShoes.brand);
}
}
|
cs |
나이키 슈즈 클래스의 인스턴스를 신어볼 수 있는 method가 위와 같이 완성되었습니다.
그러면 잘 작동하는지 보겠습니다.
1
2
3
|
Customer customer = new Customer();
customer.tryNikeShoes(new NikeShoes());
|
cs |
Customer 인스턴스를 생성하고 해당 인스턴스 참조변수에 .tryNikeShoes() method를 호출하니 정상적으로 동작하는 것을 확인할 수 있었습니다. Parameter로는 NikeShoes class의 인스턴스를 넘겨주어야 하는데, 따로 참조변수는 선언하지 않고 바로 인스턴스를 생성하여 넣어주었습니다.
실행하여 잘 작동하는 것을 확인하였습니다.
휴, 이제는 그러면 나머지 자손 class도 신어볼 수 있도록 method를 작성하면 다 신어볼 수 있겠죠?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Customer {
//NikeShoes 인스턴스를 신는 method
void tryNikeShoes(NikeShoes nikeShoes) {
System.out.println("Customer is tyring " + nikeShoes.brand);
}
//NBShoes 인스턴스를 신는 method
void tryNikeShoes(NBShoes nbShoes) {
System.out.println("Customer is trying " + nbShoes.brand);
}
//AdidasShoes 인스턴스를 신는 method
void tryAdidasShoes(AdidasShoes adidasShoes) {
System.out.println("Customer is trying " + adidasShoes.brand);
}
}
|
cs |
하지만 이렇게 일일이 method를 만들기에는 너무 많은 시간이 걸리고 비슷한 내용이 반복되게 됩니다.
이럴 때 3가지 자손 class의 공통 부모 class 인 Shoes class instance를 parameter로 받는 method를 만들면 이와 같은 문제를 해결할 수 있습니다.
1
2
3
4
5
6
|
class Customer {
void tryShoes(Shoes shoes) {
System.out.println("Customer is trying " + shoes.brand + " now.");
}
}
|
cs |
부모 class 인스턴스를 parameter로 받게 하였더니 훨씬 코드가 깔끔해졌죠. 뿐만 아니라 수행하는 기능은 아까와 동일하답니다.
1
2
3
4
5
|
Customer customer = new Customer();
customer.tryShoes(new NikeShoes());
customer.tryShoes(new NBShoes());
customer.tryShoes(new AdidasShoes());
|
cs |
아까와 마찬가지로 customer instance를 사용하여 method를 호출하고, parameter에 원하는 자손 class instance를 바로 생성하여 넣어주면 됩니다. (instance의 참조변수를 만들어서 넣어주어도 됩니다.)
이러한 방식이 바로 polymorphism(다형성)과 typecasting(형변환)을 이용한 사례입니다. 부모 class 참조 변수 자리에 자손 instance를 사용하였으니 upcasting이라고 할 수 있습니다.
동일한 부모 class를 상속하는 자식 class 인스턴스를 같은 배열로 관리하기 위해서도 다형성을 이용할 수 있습니다.
Shoes[] myShoesCollection = {new NikeShoes(), new AdidasShoes(), new NBShoes()};
이런식으로 말이죠! 때에 따라 잘 활용한다면 정말 활용성이 높아질 것 같습니다.
그렇다면 upcasting은 어느 정도 알겠는데, downcasting은 왜 사용하는 걸까요?
먼저 downcasting이 있으려면 upcasting이 선행되었다는 전제가 있어야 합니다. Upcasting을 하게되면 공통 부모 class로 묶을 수 있다는 편리함은 있지만, 각 자손 class만이 가진 고유한 method를 사용하지 못한다는 한계가 존재합니다.
그렇기 때문에 그런 한계를 보완하기 위해 다시 downcasting을 하여 자손 class 고유의 method를 수행할 수 있게끔 하는 것입니다.
이렇게 upcasting과 downcasting에 대해 좀 더 자세히 알아보았습니다.
상속과 타입캐스팅을 지속적으로 공부하면서 알게되는 내용, 헷갈리는 내용이 있다면 공부해서 이어서 업데이트 하도록 하겠습니다.
읽어보시고 궁금하신 사항 있으시면 알려주세요 같이 생각해봐요!
감사합니다. ☺️
헷갈리던 내용 1.
*그런데 조상 class에서 private하게 생성된 fields는 사실 자손 class 참조변수가 this. 또는 super. 이런 식으로 직접적으로 접근할 수가 없습니다. 물론 외부로부터의 접근을 막기 위한 것이 private의 역할이라고는 하지만, 자손 클래스가 접근할 수 없는데도 자손은 부모의 모든 인스턴스들을 상속받은게 맞는 건가? 싶은 의문이 들었습니다. 그래서 구글링을 해보니 상속이 되었을 경우 자손 클래스는 부모 클래스의 모든 인스턴스를 상속 받기는 하지만, 직접적으로 접근할 수는 없다고 합니다. 상속 관계가 아닌 다른 클래스에서 접근하듯이 getter나 setter를 통해서 접근이 가능하다고 합니다. 아니면 "protected" keyword를 사용해서 부모클래스에서 변수를 만들면 그 변수들에는 접근이 가능하다고 합니다. 아직도 조금 의문이기는 하지만, 모든 인스턴스를 다 상속 받는다는 것은 맞다고 하네요!
https://www.quora.com/Are-the-private-variables-inherited-in-child-classes-in-Java
*참고 내용
https://www.javatpoint.com/upcasting-and-downcasting-in-java
'Java' 카테고리의 다른 글
Autoboxing & Unboxing (2) | 2022.05.05 |
---|---|
Array-배열 (0) | 2022.05.02 |
Class(6)-Polymorphism(다형성) (0) | 2022.04.29 |
Class(5)-Encapsulation(캡슐화)란? 사용 방법과 이유 (0) | 2022.04.28 |
Class(4)-Composition (2) | 2022.04.27 |