본문 바로가기

Backend/Java

[JAVA] 상속 객체 형변환(TypeCasting)과 사용

 

 


상속으로 알아보는 객체 형변환(TypeCasting)과 사용


 

 

1. 형변환(TypeCasting)


변수의 형변환은 변환하고자하는 변수앞에 (괄호)를 넣고, 괄호안에 자료형을 넣어 대입연산자 = 을 통해 이루어진다.

int j = 10;
short k = (short)j; // 큰 용량의 데이터를 작은 용량에 넣는 것은 데이터 손실

 

자료형의 크기에 따라 작은 용량의 데이터를 큰 용량에 대입연산자만을 통해 넣기도 한다.

short s = 10;
int i = s; // 작은 용량의 데이터를 큰 용량에 넣는 것은 문제 없음

 

이렇듯 강제형변환을 통해 모든 데이터들을 형변환할 수 있을 것 같지만, 서로 다른 클래스 객체간의 형변환은 허용되지 않는다.

 

class NormalA{
    int num;
}

class NormalB{
    int num;
}
NormalA a2 = (NormalA)b1; // 에러
NormalB b2 = (NormalB)a1; // 에러
		
NormalB b3 = new NormalA(); // 에러
NormalA a3 = new NormalB(); // 에러

 

위와 같은 방식으로는 형변환 시도 자체가 안된다.

 

 

 

2. 객체간의 TypeCasting _ 객체 선언


그렇다면 객체간의 형변환은 영영 시도할 수 없는 것일까?

 

꼭 그렇지만은 않다.

 

class SuperF{ }
class SubF extends SuperF{ }

 

위와 같은 SuperF 클래스와 SuperF클래스를 상속하는 SubF 클래스가 있다고 하자.

 

 

먼저, 당연하게도 자기 자신의 인스턴스와 참조변수로 만드는 객체는 자유롭게 생성이 가능하다.

예시에 사용될 예정이니 객체 또한 각각 만들어두겠다.

// 자기 자신의 인스턴스와 참조변수로 만드는 객체는 자유롭게 생성 가능
SuperF f1 = new SuperF();
SubF f2 = new SubF();

방법1. 상위클래스의 참조변수에 하위클래스의 인스턴스 주소를 저장

SuperF super1 = f2;
SuperF super2 = new SubF();

 

상위클래스 참조변수인 super1과 super2를 각각 선언하고, 이에 하위클래스인 SubF 객체를 대입하거나, new 연산자를 통해 하위클래스를 생성할 수 있다.

 

SubF sub1 = f1;
SubF sub2 = new SuperF();

 

반대로 하위클래스의 참조변수에 상위클래스의 인스턴스 주소는 저장할 수 없다.

 

SuperF super3 = new SuperF();
SubF sub3 = (SubF)super3; // 런타임 에러

 

강제캐스팅(SubF)을 통해 문법상의 오류는 없앨 수 있지만 실행하면 런타임 오류가 발생한다.


방법2. 상위클래스 참조변수가 하위클래스의 인스턴스 주소를 갖고 있는 경우, 상위클래스 참조변수에 지정된 값을 하위클래스 참조변수에 옮겨 담을 수 있다.

SuperF super4 = new SubF();
SubF sub4 = (SubF)super4; // sub4에 저장되는 건 하위클래스의 인스턴스 주소이기 때문에 가능

 

무슨 말인가 함은... 방법1에서처럼 상위클래스 참조변수(super4)가 하위클래스의 인스턴스 주소(new SubF())를 저장한 경우, 하위클래스 참조변수(sub4)에 강제형변환을 통해 '옮겨' 담을 수 있다. 는 말이다. 전제조건이 있는 셈.

 

결국 자식 인스턴스 주소를 자식 참조변수(주소를 담는 변수)에 담는 건 가능하지만 부모는 안 된다는 말이다.

 

정리하자면...

자식(하위) 인스턴스의 주소 → 부모(상위) 참조변수 → (타입캐스팅) → 자식(하위) 참조변수 : OK

부모(상위) 인스턴스의 주소 → 부모(상위) 참조변수 → (타입캐스팅) → 자식(하위) 참조변수 : NOT OK (런타임에러)

 

 

해당 방법은 '강제' 형변환이기 때문에 자료형을 신경써야 한다.

만약 타입캐스팅 연산에서 상위-하위간의 자료형으로 매칭되지 않으면 에러가 발생한다.

당연하게도, 타입캐스팅하려는 상위클래스 참조변수에 저장된 값이 하위클래스 인스턴스 주소가 아니어도 에러가 발생한다.

 

조금 더 확실하게 런타임 에러없이 타입캐스트를 진행하고자 한다면, ' 하위클래스 참조변수 <- (타입캐스팅) 상위클래스 참조변수 ' 관계가 가능한 관계인지 검사하면 된다.

 

SuperF super7 = new SubF();
SubF sub7;

// super7이 저장한 값은 SubF형으로 타입캐스팅이 가능한가?
if(super7 instanceof SubF) {
    sub7 = (SubF)super7;
    System.out.println("super7 형변환 성공!");
}else {
    System.out.println("super7 형변환 실패!");
}

 

instanceof 연산자를 이용하면 객체가 클래스의 속하는지 여부와 객체의 상속관계를 알 수 있다. 

직역하면 super7은 SubF의 인스턴스 객체인가? 의 뜻으로, 위와같이 if문을 통해 코드를 작성한다면 상위클래스 객체인 super7이 하위클래스인 SubF의 인스턴스 주소값을 갖고 있어야만 형변환이 이루어지게 된다.

 

SuperF super8 = new SuperF(); // 부모 인스턴스가 들어있는 super8은 실패
SubF sub8;

// super8이 저장한 값은 SubF형으로 타입캐스팅이 가능한가?
if(super8 instanceof SubF) {
    sub8 = (SubF)super8;
    System.out.println("super8 형변환 성공!");
}else {
    System.out.println("super8 형변환 실패!");
}

 

 

 

3. 객체간의 TypeCasting _ 사용


만약 이러한 방법들로 타입캐스팅이 이루어진 경우, 경우에 따라 클래스 안에 있는 변수와 메소드를 자유롭게 사용하지는 못한다.

 

 

class SuperF{
    int superNum;
    void superMethod() {
        System.out.println("SuperMethod 호출");
    }
}

class SubF extends SuperF{
    int subNum;
    void superMethod() {
        System.out.println("SuperMethod 호출");
    }
}

 

해당 클래스를 이용해 알아보자.

 

SuperF super5 = new SubF();

super5.superNum = 100;
super5.superMethod();
// super5.subNum = 200; // 에러
// super5.subMethod(); // 에러

 

예제의 super5 는 하위클래스(SubF)의 인스턴스 주소를 담았다. 이 경우 super5는 상위클래스인 SuperF에서 물려준 멤버만 접근을 할 수 있다. 하위클래스에서 새롭게 만들어진 subNum, subMethod()는 사용할 수 없는 것이다.

 

SubF sub5 = (SubF)super5;

sub5.subNum = 300;
sub5.subMethod();
sub5.superNum = 100;
sub5.superMethod();

 

하위클래스를 참조하는 super5를 하위클래스 객체 sub5에 강제형변환을 통해 대입했다. 이 경우는 상위하위 할 것 없이 모든 클래스를 이용가능하다.


오버라이딩과도 재미난 관계가 있는데...

 

class SuperF{
    int superNum;
    void superMethod() {
        System.out.println("SuperMethod 호출");
    }
}

class SubF extends SuperF{
    int subNum;
    void subMethod() {
        System.out.println("SubMethod 호출");
    }

    // 상위 클래스의 메소드를 오버라이딩
    void superMethod() {
        System.out.println("오버라이딩 된 SuperMethod 호출");
    }
}

 

SubF 클래스에 상위클래스 메소드를 오버라이딩한 superMethod()를 추가했다.

 

SuperF super6 = new SubF();
super6.superMethod(); // 오버라이딩의 경우 접근 가능

 

하위클래스 인스턴스 주소를 저장한 상위클래스 객체 super6는 앞서 다루었던 것처럼 하위클래스의 메소드를 실행할 수 없다. 그러나 하위클래스에서 상위클래스의 메소드를 오버라이딩한 경우는 접근이 가능하다. (와!)

상위클래스 참조변수로 상위클래스 메소드를 호출했는데 하위클래스의 오버라이딩된 메소드가 우선 실행되는 것이다.