개발하는 몽당연필

업캐스팅(UpCasting) & 다운캐스팅(DownCasting) 본문

CS/JAVA

업캐스팅(UpCasting) & 다운캐스팅(DownCasting)

엘밍 2023. 6. 25. 03:41

📎  캐스팅 (Casting)

📌 캐스팅이란 ?

  • 하나의 데이터 타입을 다른 타입으로 바꾸는 것을 말합니다.
  • 캐스팅은 타입 변환 혹은 형변환 이라고 합니다.

 

 

 

📌 자바의 데이터형

먼저 자바의 데이터형에 대해 알아봅시다.

자바의 데이터형은 크게 두가지로 나뉩니다.

기본형 (Primitive Type) Boolean Type (boolean), Numeric Type (short, int, long, float, double, char)
참조형 (Reference Type) Class Type, Interface Type, Array Type, Enum Type, etc...

 

 

 

 

 

📌 캐스팅이 필요한 이유?

1. 다형성 : 오버라이딩된 함수를 분리해서 활용할 수 있다.
2. 상속 : 캐스팅을 통해 범용적인 프로그래밍이 가능하다.
더보기

다형성이란?

하나의 타입에 여러 객체를 대입할 수 있다는 의미

 

업캐스팅은 다형성과 관련이 깊다.

업캐스팅함으로써 자식 클래스 객체가 부모 클래스 타입으로 형변환되므로,

메서드 여러개를 오버로딩하지 않고도 같은 동작을 구현할 수 있다.

따라서 코드 변경이 용이하고, 유지보수성이 올라간다.

 

자바에서는 대입 연산자 (=)를 사용할 때,

변수와 값 양쪽 타입이 일치하지 않으면 할당이 불가능합니다.

프로그램에서 값의 대입이나 연산을 수행할 때는 같은 타입끼리만 가능하기 때문입니다.

 

long num = 7.24; // ERROR

long num = (long) 7.24; // Success

따라서 위 예제와 같이 캐스팅 연산자를 사용하여 강제적으로 타입을 지정해 변수에 대입하도록 설정해줄 수 있습니다.

 

 

 

 

 

 

📌 참조변수의 형변환

기본형 타입을 서로 형변환할 수 있듯이, 자바의 상속 관계에 있는 부모와 자식 클래스 간에는 서로 간의 형변환이 가능합니다.

클래스는 reference 타입으로 분류되기때문에 이를 참조형 캐스팅 (업캐스팅 / 다운캐스팅) 이라고 부릅니다.

 

 

자바 클래스의 객체는 부모 클래스를 상속하고 있기 때문에 부모의 멤버를 모두 가지고 있습니다.

반면 부모 클래스의 객체는 자식 클래스의 멤버를 모두 가지고 있지는 않습니다..

 

즉, 참조변수의 형변환은 사용할 수 있는 멤버의 갯수를 조절하는 것입니다.

예를 들어, 기본형 타입의 형변환(실수 → 정수)은 값(3.6 3)이 바뀌게 됩니다.

그렇지만 객체 형변환 멤버 갯수만 달라지게 됩니다.

참조 캐스팅은 이러한 클래스의 멤버 구성 관점에서 판별하면 와닿기 쉬울 것입니다.

 

class Parent {
	String name;
	int age;
}

class Child extends Parent {
	/*
	String name;
	int age;
	*/
	int number;
}

Parent p = new Parent(); 
Child c = new Child();

Parent p2 = (Parent)c; // 업캐스팅 - 자식에서 부모로
Child c2 = (Child)p2; // 다운캐스팅 - 부모에서 자식으로

Parent 객체는 Child 객체에 상속을 받고 있으니 더 상위 요소로 판별될 수 있습니다.

따라서 UpCasting 이라고 합니다.

반대로 하위 요소인 자식 객체로 형변환하는 것은 DownCasting 이라고 합니다.

 

 

이러한 참조형 캐스팅의 특징으로는, 대표적으로 ArrayList 자료형 선언문을 보면 알 수 있습니다.

다음은 자바 프로그래밍에서 리스트 자료형을 다음과 같이 선언하는 코드입니다.

List<int> list = new ArrayList()<>;

ArrayList를 List로 변수 타입을 선언해도 문제가 없는 이유는 ArrayList가 List를 부모 클래스로서 상속받기 때문입니다.

위 코드도 업캐스팅이라고 볼 수 있습니다.

 

한가지 주의해야할 점은, 같은 부모 클래스를 상속받고 있더라도 형제 클래스끼리는 아예 타입이 다르기 때문에 참조형변환이 불가능하다는 점입니다.

 

class Car {
	String color;
    int door;
    
    void drive(){
    	System.out.println("drive, Brrrr~");
   	}
    
    void stop(){
    	System.out.println("stop!!!!!");
   	}
}

clas FireEngine extends Car {
	void drive(){
    	System.out.println("drive, FireEngine!");
   	}

	void water() {
    	System.out.println("water~!!");
   	}
}

clas Ambulance extends Car {
	void drive(){
    	System.out.println("drive, Ambulance!");
   	}

	void siren() {
    	System.out.println("Beep Beep ~!!");
   	}
}
FireEngine f = new FireEngine();

Car c = (Car) f;				// OK. 부모인 Car 타입으로 형변환 (생략가능)
FireEngine f2 = (FireEngine) c; // OK. 자식인 FireEngine 타입으로 형변환 (생략불가)
Ambulance a = (Ambulance) f;	// ERROR. 상속관계가 아닌 클래스 간의 형변환 불가 (형제관계)

위 코드에서 볼 수 있듯이 형제 클래스끼리는 형변환이 불가능한 것을 볼 수 있습니다.

 

 

 

 

 

 

 

 

📎  업캐스팅 (UpCasting)

업캐스팅이란 자식 클래스가 부모 클래스 타입으로 캐스팅 되는 것입니다.

 

 

📌 업캐스팅 특징 요약

  • 업캐스팅은 캐스팅 연산자 괄호를 생략 가능
  • 부모 클래스로 캐스팅 된다는 것은 멤버의 갯수 감소를 의미함
    이는 곧 자식 클래스에서만 있는 속성과 메서드는 실행하지 못한다는 뜻
  • 업캐스팅을 하고 메소드를 실행할 때, 만일 자식 클래스에서 오버라이딩한 메서드가 있을 경우, 부모 클래스의 메서드가 아닌 오버라이딩된 메서드가 실행되게 됨

 

 

Car c = new FireEngine();	// 참조 다형성

FireEngine f = new FireEngine();
Car c2 = f;					// 업캐스팅

객체 지향(oop)의 참조 다형성을 알고있다면 업캐스팅을 더 이해하기 쉽습니다.

둘은 단지 한번에 대입하는지(참조 다형성), 변수에 나눠 대입하는지(업캐스팅)의 차이가 있을뿐입니다.

 

 

 

 

 

📌 업캐스팅 멤버 접근 제한

멤버가 많은 자식 클래스에서 부모 클래스로 업캐스팅 했으니 당연히 멤버 갯수가 감소하게 됩니다.

이는 실행할 수 있는 속성과 메서드가 제한된다는 뜻이기도 합니다.

 

 

위에서 c2 참조 변수에 할당한 데이터 f 변수는 FireEngine 객체입니다.

그런데 업캐스팅되면서 Car 타입으로 형변환 되었기 때문에 부모 클래스(Car)에 속한 멤버만 접근이 허용되게 제한됩니다.

c2.water();	// compile error

 

이 코드에서 부모 클래스인 Car에는 없고 자식 클래스인 FireEngine에만 있는 water() 메서드를 실행해보면,

컴파일 에러가 발생하게 됩니다. (컴파일단에서 ERROR 처리가 되기때문에 바로 수정이 가능)

 

 

즉, 업캐스팅을 하게되면 부모 클래스 멤버로 멤버 갯수가 한정되었기 때문에 자식 클래스 고유의 메서드는 사용이 불가능합니다.

이는 메서드 뿐만이 아니라 멤버 필드에도 동일하게 적용됩니다.

 

 

객체를 업캐스팅 하면 자식과 부모의 공통된 것만 사용 가능하고,
자식 클래스에서 새로 만들어진 것은 사용이 불가능하다.

 

 

 

 

 

📌 업캐스팅 오버라이딩 메서드

 

이번에는 부모 클래스에도 있고 자식 클래스에도 재정의 되어있는 drive() 메서드를 실행시켜봅시다.

 

c2.drive();

====================
"drive, FireEngine!"

 

c2는 업캐스팅 되었기 때문에 부모 클래스에 정의된 메서드를 사용할 것 같지만,

오버라이딩된 자식 클래스의 메서드를 사용하는 것을 볼수 있습니다.

이는 오버라이딩 특성상 코드가 실행하는 런타임 환경에서 동적으로 바인딩 되었기 때문입니다.

 

 

 

 

📌 왜 업캐스팅을 사용해야할까?

업캐스팅을 사용하는 이유는 공통적으로 할 수 있는 부분을 만들어 간단하게 다루기 위해서입니다.

상속 관계에서 상속받은 서브 클래스가 몇개이든 하나의 인스턴스로 묶어서 관리할 수 있기 때문입니다.

 

 

위에서 나온 Car, FireEngine, Ambulance 클래스를 사용하여 예시를 들어보겠습니다.

 

FireEngine[] f = new FireEngine[];
f[0] = new FireEngine();
f[1] = new FireEngine();

Ambulance[] a = new Ambulance[];
a[0] = new Ambulance();
a[1] = new Ambulance();

업캐스팅을 사용하지 않는다면 위의 코드에서 볼 수 있듯이, FireEngine, Ambulance 클래스는 서로 다른 타입이므로 각각의 타입을 정의해서 사용해줘야합니다.

 

Car[] c = new Car[];
c[0] = new FireEngine();
c[1] = new FireEngine();
c[2] = new Ambulance();
c[3] = new Ambulance();

하지만 상속 관계를 맺어 부모 클래스로 업캐스팅 해준다면, 위와 같이 하나의 부모 클래스 타입으로 묶어 배열을 구성할 수 있습니다.

 

 

이렇게 하나의 자료형으로 관리함으로써

  • 코드량도 훨씬 줄어들고
  • 가독성도 좋아졌으며
  • 유지보수성도 좋아졌습니다.

 

 

하지만...

오버라이딩한 메서드가 아닌 이상 업캐스팅한 부모 클래스 타입에서 자식 클래스의 고유 메소드를 실행할 수 없습니다.

따라서 업캐스팅한 객체를 다시 자식 클래스 타입으로 되돌리는 다운 캐스팅이 필요합니다.

 

 

 

 

 

📎  다운캐스팅 (DownCasting)

다운캐스팅이란 거꾸로 부모 클래스가 자식 클래스 타입으로 캐스팅되는 것입니다.

단순히 업캐스팅의 반대 개념이 아닙니다.

 

다운 캐스팅의 진정한 의미는 부모 클래스로 업캐스팅된 자식 클래스를 복구하여, 본인의 필드와 기능을 회복하기 위해 있는 것입니다.

즉, 원래있던 기능을 회복하기 위해 다운캐스팅을 하는 것입니다.

 

 

📌 다운캐스팅 특징 요약

  • 다운캐스팅은 캐스팅 연산자 괄호 생략 불가능
  • 다운캐스팅의 목적은 업캐스팅한 객체를 다시 자식 클래스 타입의 객체로 되돌리는 것 (복구)

 

 

 

Car c;
FireEngine f = new FireEngine();

c = f;	// 업캐스팅

FireEngine f2 = (FireEngine) c;	// 다운캐스팅 (캐스팅 연산자 생략 불가능)
f2.drive();	// "drive, FireEngine!"
f2.water();	// "water~!!


((FireEngine) c).water(); // "water~!!" -> 한줄 표현

업캐스팅된 객체 c 에서 만약 자식 클래스에만 있는 water() 메서드를 실행해야한다면,

다운캐스팅을 통해 자식 클래스 타입으로 회귀시킨뒤 메서드를 실행하면 된다.

만약 메서드를 한번만 실행할 것이라 따로 변수에 저장해둘 필요가 없으면 한줄로 표현할 수도 있다.

 

이 때, 업캐스팅과 달리 캐스팅 연산자를 생략할 수 없다.

다운캐스팅은 사용할 수 있는 객체 멤버가 증가되는 것을 의미하는데,

실제 참조변수가 가리키는 객체가 무엇인지 모르기 때문에 캐스팅 연산자를 생략한다면 어떠한 멤버가 추가되는지 알수가 없다.

그래서 반드시 형변환 괄호를 기재하여 증가된 클래스의 멤버가 무엇인지 알려줘야한다.

 

 

 

 

 

📌 다운캐스팅 주의점

다운캐스팅의 목적은 업캐스팅한 객체를 되돌리는 것이다.

따라서 업캐스팅 되지 않는 생 부모 객체를 그래도 다운캐스팅하면 오류(ClassCastException)가 발생하게 된다.

 

Car c = new Car();

FireEngine f = (FireEngine) c;	// Runtime Error - Car cannot be cast to FireEngine
f.drive();	// runtime error
f.water();	// runtime error

 

에디터에서 컴파일 에러가 발생하지 않고 런타임 에러가 발생하는 위험성이 있기 때문에 이와같은 다운캐스팅의 특성은 더욱 주의해야한다.

 

기본형 캐스팅은 값의 손실만 있을 뿐 프로그램이 작동하는데에는 문제가 없지만, (3.14 → 3)

다운캐스팅은 에디터에서는 빨간줄이 없는데 코드를 실행 도중에 갑자기 에러가 터져 프로그램이 죽어버릴 수 있다.

 

 

 

 

 

 

 

📎  instanceof

하나의 부모 클래스는 여러 자식 클래스를 가질 수 있고, 자식 클래스는 어떤 클래스의 부모 클래스가 될수도 있다.

이처럼 상속관계가 나무처럼 복잡하게 얽혀있는 상황이 발생하게되면, 캐스팅 할 때 해당 객체의 타입을 파악하기 어렵게 한다.

 

참조캐스팅을 잘못하게되어 런타임 환경에서 에러가 발생해 프로그램이 종료되면 서비스에 큰 차질이 생긴다.

코드 디버깅을 많이하여 미리 예방하는 것이 좋지만, 이마저도 부족하다면 직접 업/다운 캐스팅 유무를 확인하여 참조 캐스팅 동작을 결정하면 된다.

 

 

이 때 사용되는 것이 instanceof 연산자이다.

 instanceof는 어느 객체 변수가 어느 클래스 타입인지 판별해 true / false 를 반환해준다.

 

instanceof 연산자는 객체에 대한 클래스(참조형) 타입에만 사용할 수 있다는 점을 주의해야한다. (int, double 같은 primitive 타입에 사용 불가능)

 

Car c = new FireEngine(); // 업캐스팅
FireEngine f = new FireEngine();

if(c instanceof FireEngine) // True
if(f instanceof Animal)		// True -> FireEngine이 Car를 상속받았으니까!
if(f instanceof FireEngine)	// True

 

 

 

 

 

 

 

 


 

📖 Reference

 

☕ JAVA 업캐스팅 & 다운캐스팅 - 완벽 이해하기

자바의 참조형 캐스팅 하나의 데이터 타입을 다른 타입으로 바꾸는 것을 타입 변환 혹은 형변환(캐스팅) 이라고 한다. 자바의 데이터형을 알아보면 크게 두가지로 나뉘게 된다. 기본형(primitive ty

inpa.tistory.com

 

'CS > JAVA' 카테고리의 다른 글

직렬화 (Serialization)  (1) 2023.07.25
Call by value & Call by reference  (0) 2023.06.26
자바 가상 머신 (Java Virtual Machine)  (0) 2023.06.19
자바 컴파일 과정  (1) 2023.06.19
Stream (스트림)  (4) 2023.06.05