1. 참조 자료형의 형 변환

이 절의 실습을 위해서 앞 절에서 사용한 Parent와 Child 클래스를 활용하겠습니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package c.inheritance;
    
public class Parent {
    
    Parent(){
    }
        
    public Parent(String name) {
        System.out.println("Parent(String) Constructor");
    }
        
    public void printName() {
        System.out.println("printName() - Parent");
    }    
}

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package c.inheritance;
 
public class Child extends Parent{
    
    public Child() {
    }
    
    public Child(String name) {
    }
    
    public void printAge() {
        System.out.println("printAge() - 18 month");
    }
    
    public void printName() {
        System.out.println("printName() - Child");
    }
}

 

 

지금까지는 객체를 생성할 때 다음과 같이 만들었습니다.

 

1
2
Parent parent = new Parent();
Child child = new Child();

 

 

그런데 상속 관계가 성립되면, 지금까지 객체를 생성한 것과는 다르게, 다음과 같이 객체를 생성할 수도 있습니다.

 

1
Parent obj = new Child();

 

 

하지만 다음과 같은 객체 생성은 안됩니다.

 

1
Child obj2 = new Parent();

 

 

자식 클래스인 Child 클래스에서는 부모 클래스인 Parent 클래스에 있는 메소드와 변수들을 사용할 수 있습니다. 그런데, 거꾸로 부모 클래스인 Parent 클래스에서는 Child 클래스에 있는 모든 메소드와 변수들을 사용할 수 없습니다. 

 

이 부분에 대한 개념을 잡는 것은 매우 중요합니다. Parent 클래스에서는 Child 클래스에 있는 모든 메소드와 변수를 사용할 수도 있고, 그렇지 않을 수도 있습니다. 만약 Child 클래스에 추가된 메소드나 변수가 없으면 가능할 수도 있습니다. 하지만, 자바 컴파일러에서는 자식 객체를 생성할 때 부모 생성자를 사용하면 안 된다고 못을 박아 버립니다. 명시적으로 형 변환을 한다고 알려줘야만 합니다.

 

참조 자료형은 자식 클래스의 타입을 부모 클래스의 타입으로 형 변환하면 부모 클래스에서 호출할 수 있는 메소드들은 자식 클래스에서도 호출할 수 있으므로 전혀 문제가 안됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package c.inheritance;
 
public class Inheritance {
 
    public static void main(String[] args) {
        
        Parent parent = new Parent();
        Child child = new Child();
        
        Parent parent2 = child;
        Child child2 = parent;
    }
    
}

 

 

컴파일을 해보면 자식 클래스 타입에서 부모 클래스 타입으로 형 변환을 하면 문제가 없고, 부모 클래스 타입에서 자식 클래스 타입으로 형 변환을 하면 안되므로 에러가 발생할 것 입니다. 왜냐하면 parent 객체는 Parent 클래스의 객체이며, Child 클래스에 선언되어 있는 메소드나 변수를 완전히 사용할 수 없기 때문입니다. 따라서 컴파일 오류만을 피하려면 다음과 같이 형 변환을 해야합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package c.inheritance;
 
public class Inheritance {
 
    public static void main(String[] args) {
        
        Parent parent = new Parent();
        Child child = new Child();
        
        Parent parent2 = child;
        Child child2 = (Child) parent;
    }
    
}

 

 

이제 컴파일은 제대로 되지만 다음과 같은 에러가 발생합니다.

 

 

 

왜냐하면, parent 객체는 실제로는 Parent 클래스의 객체이므로 컴파일 오류는 넘겼지만, 실행시에는 얘는 원래 Parent 클래스의 객체라서 못쓰겠다는 예외가 발생한 것입니다. 코드를 다음과 같이 수정해봅시다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package c.inheritance;
 
public class Inheritance {
 
    public static void main(String[] args) {
        
        //Parent parent = new Parent();
        Child child = new Child();
        
        Parent parent2 = child;
        //Child child2 = (Child) parent;
        Child child2 = (Child)parent2;
    }
    
}

 

 

문제 없이 실행 될 것입니다. Parent parent2 = child;는 child를 대입한 것입니다. 그리고, child는 Child 클래스의 객체입니다. parent2는 겉모습은 Parent 클래스의 객체인 것처럼 보이지만, 실제로는 Child 클래스의 객체이기 때문에 parent2를 Child 클래스로 형 변환해도 전혀 문제는 없습니다.

 

이렇게 형 변환하면 된다는 것을 알았는데, 왜 이렇게 머리 아프게 써야 하는 것일까요? 다음의 메소드를 봅시다.

 

1
2
3
4
5
6
7
8
9
10
11
12
package c.inheritance;
 
public class Inheritance {
    
    public void objectCast2() {
        Parent[] parentArray = new Parent[3];
        parentArray[0= new Child();
        parentArray[1= new Parent();
        parentArray[2= new Child();
    }
 
}

 

 

Parent 배열은 3개의 값을 저장할 수 있습니다. 그런데 0번쨰와 2번쨰 배열은 Child 클래스의 객체를 할당한 것을볼 수 있습니다. 이렇게 코딩해도 전혀 문제 없습니다. 이와 같이 일반적으로 여러 개의 값을 처리하거나, 매개 변수로 값을 전달 할 때에는 보통 부모 클래스 타입으로 보냅니다. 이렇게 하지 않으면 배열과 같이 여러 값을 한번에 보낼 때 각 타입별로 구분해서 메소드를 만들어야 하는 문제가 생길 수도 있기 때문입니다. 

 

그런데, parentArray라는 배열의 타입이 Child인지 Parent인지 어떻게 구분할 수 있을까요? 이런 경우에 사용하는 것이 "instanceof"라는 예약어 입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package c.inheritance;
 
public class Inheritance {
    
    public void objectCast2() {
        Parent[] parentArray = new Parent[3];
        parentArray[0= new Child();
        parentArray[1= new Parent();
        parentArray[2= new Child();
        
        for(Parent tempParent:parentArray) {
            if(tempParent instanceof Child)
                System.out.println("Child");
            else if(tempParent instanceof Parent)
                System.out.println("Parent");
        }    
    }
 
    public static void main(String[] args) {
        Inheritance inhe = new Inheritance();
        inhe.objectCast2();
    }
}

 

 

 

이렇게 instanceof의 앞에는 객체를, 뒤에는 클래스(타입)를 지정해 주면 됩니다. 그러면 이 문장은 true나 false와 같이 boolean 형태의 결과를 제공합니다. 

 

이제 Child 클래스에만 있는 printAge() 메소드를 이 예제에서 호출하도록 변경해보겠습니다.

 

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
package c.inheritance;
 
public class Inheritance {
    
    public void objectCast2() {
        Parent[] parentArray = new Parent[3];
        parentArray[0= new Child();
        parentArray[1= new Parent();
        parentArray[2= new Child();
        
        for(Parent tempParent:parentArray) {
            if(tempParent instanceof Child) {
                //System.out.println("Child");
                Child tempChild = (Child) tempParent;
                tempChild.printAge();
            } else if(tempParent instanceof Parent) {
                System.out.println("Parent");
            }
        }    
    }
    
    public static void main(String[] args) {
        Inheritance inhe = new Inheritance();
        inhe.objectCast2();
    }
}

 

 

만약 타입이 Parent인 객체는 printAge() 메소드가 없으므로, 무작정 형 변환하여 printAge() 메소드를 호출하면 실행시에 분명히 에러가 발새할 것입니다. 이렇게 instanceof를 사용하면 정확하게 타입을 확인할 수 있고, 원하는 메소드를 호출할 수 있습니다. 

 

그런데, instanceof를 사용하면서 유의할 점이 하나 있습니다. parentArray의 0번째 값은 분명히 Child 타입입니다. 그런데, Parent 타입도 될까요? 당연히 true입니다. 그렇기 떄문에 이 예제에서 instanceof로 타입을 점검할 때 Parent의 인스턴스인지 여부를 먼저 점검하면 안됩니다. 

 

정리 해보겠습니다.

 

● 참조 자료형도 형 변환이 가능하다.

● 자식 타입의 객체를 부모 타입으로 형 변환 하는 것은 자동으로 된다.

● 부모 타입의 객체를 자식 타입으로 형 변환을 할 때에는 명시적으로 타입을 지정해 주어야 한다. 이때, 부모 타입의 실제 객체는 자식 타입이어야 한다.

● instanceof 예약어를 사용하면 객체의 타입을 확인할 수 있다.

● instanceof로 타입 확인을 할 떄 부모 타입도 true라는 결과를 제공한다.

 

객체의 형 변환은 중요합니다. 내용이 이해가 안된다면 꼭 이해하고 다음으로 넘어가시기 바랍니다!

 

'Java' 카테고리의 다른 글

[Java] 15. Object 클래스  (0) 2019.03.30
[Java] 14. 다형성(Polymorphism)  (0) 2019.03.26
[Java] 12. 메소드 오버라이딩(Overriding)  (0) 2019.03.23
[Java] 11. 상속  (0) 2019.03.21
[Java] 10. 접근 제어자  (0) 2019.03.19
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기