다형성
다형성은 객체지향의 특징중 하나이다(객체지향의 특징 : 캡슐화, 다형성, 상속성, 동적바인딩, 추상화). 다형성이란, 여러가지 형태를 가질 수 있는 능력을 의미한다. 자바에서 다형성이란, 한 타입의 참조변수로 여러 타입 객체를 참조하는것을 의미한다. 좀 더 자세한 의미로는 조상클래스 타입의 참조변수로 자손타입 인스턴스를 참조할 수 있다. 아래코드를 예시로 들어보자
class tv{
private boolean power;
private int channel;
tv(int channel){
this.channel = channel;
}
public boolean getPower(){
return this.power;
}
public int getChannel(){
return this.channel;
}
public void setPower(){
this.power = !this.power;
}
}
class CaptionTv extends tv{
CaptionTv(){
super(10);
}
CaptionTv(int channel){
super(channel);
}
private String text;
public String getText(){
return this.text;
}
void showCaption(){
System.out.println(this.getText());
}
}
tv와 CaptionTv클래스가 서로 상속관계에 있을 경우, 다음과 같이 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하는것이 가능하다.
tv t = new CaptionTv();
CaptionTv a = new CaptionTv();
위에서 t참조변수는 CaptionTv의 모든 멤버에 접근할 수 없다. tv에 존재하는 CaptionTv의 멤버만 접근할 수 있다. 그렇기에 t는 text멤버변수, getText(), showCaption()에 접근이 불가능하다. 반면 CaptionTv형 참조변수 a는 CaptionTv의 모든 멤버에 접근가능하다. 즉, 같은 타입 인스턴스여도, 참조변수 타입에 따라 사용할 수 있는 멤버개수가 달라지는 것이다. 이러한 경우는 어떻게 될까?
CaptionTv c = new tv();
위 같은 경우에는 에러가 난다, 이유는 참조변수 c가 사용할 수 있는 멤버 개수가 tv()인스턴스보다 많기 때문이다. 결론적으로 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스 멤버개수보다 같거나 적어야한다. 또한 조상타입 참조변수로 자손타입의 인스턴스를 참조할 수 있지만, 반대로 자손타입 참조변수로 조상타입의 인스턴스를 참조할 수 없는것이다(상속받은 CaptionTv멤버 수가 tv멤버수보다 많기 때문에 성립하지 않음).
만약에 자손클래스에 조상클래스의 메소드가 오버라이딩 되어있다면, 참조변수는 조상클래스의 메소드가 아닌, 자손클래스의 오버라이딩 된 메소드를 읽어온다. 반면 멤버변수 같은 경우에는 조상클래스의 멤버변수를 읽어온다.
/**
*
* 조상타입의 참조변수와 자손타입 참조변수 차이점이 사용할 수 있는 멤버의 개수인것을 알았다.
*조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자손 클래스에 중복으로 정의하고, 조상타입으로 자손 인스턴스를 참조하게 되면 어떻게 될까?
*
* 메소드 같은 경우에는 오버라이딩 되어 중복된 경우에는 무조건 자손타입의 메소드를 사용하게 된다.
* 멤버변수가 조상클래스, 자손클래스 중복 정의된 경우 조상타입의 참조변수를 사용했을때 조상 클래스에 선언된 멤버변수가사용되고, 자손타입 참조변수 사용시, 자손클래스 멤버변수가 사용된다.
*/
class Parent{
int x = 200;
void method(){
System.out.println("Parent Method");
}
void method2(){}
}
class Child extends Parent{
int x = 300;
@Override
void method(){
System.out.println("Child Method");
}
@Override
void method2(){
System.out.println("x = " + x);
System.out.println("super.x = " + super.x);
System.out.println("this.x = " + this.x);
}
}
public class instanceof2 {
// 멤버변수는 참조변수의 자료형에 따라서 값이 달라진다.
public static void main(String[] args){
Parent p = new Child();
Child c = new Child();
System.out.println(p.x); // Parent Class 의 멤버변수
System.out.println(c.x);// Child Class의 멤버변수
p.method();// child method
c.method();//child method
p.method2();
c.method2();
}
}
참조변수 형변환
참조변수 또한 형변환이 가능하다. 단, 이는 서로 상속관계에 있는 경우에만 가능하다. 가능한 형변환은 아래와같다.
- Up-Casting : 자손타입참조변수 -> 조상타입참조변수 : 형변환 생략가능
- Down- Casting : 조상타입참조변수 -> 자손타입참조변수 : 형변환 생략 불가능
이 두개에서 주목해야할 것은 그냥 '타입'이 아닌 '타입의 참조변수'라는 것이다. 이해하는데에 있어서 잘못볼 경우 혼란을 줄 수 있다는것을 조심하자. 또 다른 주의할 점은, 서로 상속관계에 있는 클래스간의 형변환은 성립하지만, 일반적인 클래스간의 관계에서는 참조변수 형변환이 불가능하다는것을 기억하자.
업캐스팅의 형변환 생략 가능의 원리는, 기본 자료형에서 작은 자료형에서 큰 자료형은 생략이 가능한것과 동일하게 생각하면 된다.(int -> float/double 생략가능, float/double -> int 생략불가능). 이 형변환을 통해, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 개수를 조절하는것이다.
업캐스팅은 아래와 같은 형태이다.
tv t = new CaptionTv();
다운캐스팅은 아래와 같은 형태이다.
CaptionTV a = new tv();
하지만 일반적으로 다운 캐스팅은 컴파일은 되지만 대부분의 경우에서 오류가 난다. 이유는 위에서 보았듯이, 자식클래스 참조변수로 조상클래스 인스턴스를 참조하면, 참조변수가 사용할 수 있는 멤버가 더 많기때문에, 성립되지 않는다. 그렇다면 다운 캐스팅은 어느경우에 성립될까? 아래와 같은 경우에는 다운캐스팅이 성립된다.
tv e = new CaptionTv();
CaptionTv a = (CaptionTv)e;
다운캐스팅은 쉽게 생각해서, 객체의 잃어버린 성격을 다시 되찾아주는것이라고 생각하면 된다. 위 예시같은 경우에는 CationTv()인스턴스지만, tv타입의 참조변수에 의해 원 성격을 잃어버리게 된다. 이걸 CaptionTv참조변수로 재참조를 함으로서 잃어버린 CaptionTv성질을 모두 복구해 주는 것이다.
참조변수 형변환을 활용한 간단한 예시 코드를 작성해 보았다.
package Interface2;
class Car{
private String color;
private final int door = 2;
Car(String color){
this.color = color;
}
void Drive(){
System.out.println("Driving now");
}
void Stop(){
System.out.println("Stop Driving");
}
}
class FireEngine extends Car{
FireEngine(){
super("Black");
}
@Override
public String toString(){
return "Fire Engine";
}
@Override
void Drive(){
System.out.println("Driving with " + this.toString());
}
@Override
void Stop(){
System.out.println("Stop driving " + this.toString());
}
void SpreadWater(){
System.out.println("Spreading Water");
}
}
class Ambulance extends Car{
Ambulance(){
super("White");
}
@Override
public String toString(){
return this.getClass().getName();
}
@Override
void Drive(){
System.out.println("Drive with " + this.toString());
}
@Override
void Stop(){
System.out.println("Stop driving " + this.toString());
}
void Siren(){
System.out.println("Start Siren");
}
}
public class polymorphismex {
public static void main(String[] args){
Car car = null;
Car car2 = new Car("White");
FireEngine fe1 = new FireEngine();
FireEngine fe2 = null;
FireEngine fe3 = null;
fe1.SpreadWater(); // 오버라이딩된 메소드 호출
car = fe1; // 조상클래스 참조변수로 자식클래스 인스턴스 참조, 형변환 명시 생략가능
//car.SpreadWater(); // 조상클래스 참조변수로 인스턴스 참조를 해주었으므로, 조상클래스의 멤버에 해당하는 메소드들만 사용이 가능하게 된다.
fe2 = (FireEngine)car; // 현재 조상클래스 참조변수 car은 자식클래스 인스턴스 fe1을 참조하고있다. 그렇기 때문에 결론적으로 이 구문은 FireEngine fe2 = fe1과 같은 형태이므로,
//잃어버린 fe1의 성질을 되찾아주는 형태의 다운캐스팅이다. 그렇기때문에, 이는 성립하는 다운캐스팅이다.
//fe3 = (FireEngine) car2; // Class Cast Exception : 조상클래스 인스턴스
Ambulance a = new Ambulance();
a.Siren();
a.Drive();
a.Stop();
}
}
'Language > Java' 카테고리의 다른 글
[Java]인터페이스 (0) | 2022.01.07 |
---|---|
[Java] 추상화란? (0) | 2022.01.04 |
[Java]제어자 (0) | 2021.12.29 |
[Java]super (1) | 2021.12.28 |
[Java]오버라이딩(Overriding) (0) | 2021.12.28 |