본문 바로가기
Programming/JAVA

[JAVA] 자바의 다형성

다형성(Polymorphism)이란, 많을 다에 형태 형을 사용한다. 여기서 말하는 '형'이란 타입을 이야기한다. 즉, 많은 형태를 가진다는 뜻. 

 

가장 유명한 예인 붕어빵으로 가정을 해보자.

붕어빵 사장은 최근 붕어빵 장사가 잘 되지 않았다. 그래서 붕어빵을 좀 더 발전시킨 황금 잉어빵으로 아이템을 변경했다. 

이떄 황금 잉어빵을 위해서 황금 잉어빵을 새로 만들진 않았을 것이다. 기존 붕어빵에서 황금색을 낼 수 있는 재료와 단 맛을 더 추가했을 것이다.

좌 붕어빵, 우 황금 잉어빵

그런데 단골 손님이 와서 붕어빵을 천 원 어치 주문했다. 지금은 황금 잉어빵을 팔고 있는데, 붕어빵을 팔 수 없는 걸까?

아마도 사장은 황금 잉어빵을 건넸을 것이다. 그리고 고객도 불만을 갖지 않을 것이다. 

사장과 손님 모두 다형성에 대해 알고 있기 때문이다. 이 이야기에서 등장하는 정확한 빵의 명칭은 황금 잉어빵이지만, 이 빵을 두고 붕어빵이라고도 하고 황금 잉어빵이라고도 한다. 이것이 다형성이다.

 

JAVA의 다형성

상속 관계에 있을 때 조상 클래스 타입(붕어빵)으로 자식 클래스 객체(황금 잉어빵)을 레퍼런스 할 수 있다. 어떤 의미일까?

예를 들어보자. 이전 포스팅에서 SpiderMan과 Person을 정의했다. SpiderMan은 Person을 상속, Person은 Object를 상속한다고 배웠다. 각 객체를 10개씩 배열에 저장해야 한다.

Person []persons = new Person[10];
SpiderMan []smans = new SpiderMan[10];

 

배열은 같은 타입의 데이터를 묶어서 관리하는 특징이 있다. 따라서 각각의 배열을 생성해야한다. 

하지만 다형성에 의해 Person 배열에 SpiderMan을 담을 수 있다. 

Person []persons = new Person[10];
persons[0] = new Person();
persons[1] = new SpiderMan();

 

다시 정리하자면 부모 클래스가 자식의 객체에 접근 가능한 것이다. 

 

 

자바의 자료구조가 간단해질 수 있는 이유

Object는 모든 객체의 조상이라고 말했다. 예를 들어, System.out.println(new Person());을 했다고 가정하자. 

`.`으로 이뤄진 것을 보아 System의 out의 println이라는 메서드를 호출한 것을 볼 수 있다. 매개변수는 new Person().

그렇다면 println()이라는 메서드는 이런 형태로 이루어져 있을까?

    public void println(Person p){ }
    public void println(SpiderMan s){ }

 

당연히 그럴 일은 없다. 일일히 모든 객체를 매개변수로 받아 출력한다면 한도 끝도 없을 것이다. 대신에, 자바는 Object를 매개변수로 받는다. 

    public void println(Object x){
        String s = String.valueOf(x);
        synchronized (this){
            print(s);
            newLine();
        }
    }

 

비밀은 바로 파라미터(매개변수)의 타입이 Object인 점이다. Object는 모든 객체의 조상이므로 어떤 객체가 들어와도 연결할 수 있다. 즉, 어떤 객체가 들어와도 처리 할 수 있다. 

 

 

이렇게 한 번 가정해보자.

Person person = new SpiderMan("스파이더 맨",20);

 

SpiderMan은 힙 영역에 있는 SpiderMan의 타입의 객체다. 이 객체는 SpiderMan의 메서드를 사용할 수 있다. 하지만, 등호의 왼쪽은 Person 타입으로 SpiderMan의 메서드를 사용할 수 있을까? 

 

 

실제로 Person의 메서드만 접근이 가능하고 SpiderMan에서 선언했던 메서드들은 보이지 않는다. 비록 실제 메모리에 있는 객체가 SpiderMan이더라도 참조하는 타입이 Person이므로 SpiderMan의 고유한기능을 모르는 것이다. 물론, 만약 Object 타입으로 참조하고 있다면 Object의 영역만 사용할 수 있을 것이다. 

 

 

어떻게하면 전체 내용을 접근할 수 있을까?

단순히 참조하는 변수의 타입을 바꿔주면 된다. 형변환. 하지만 기본형이 아니라 참조형이다. 

public class Poly {
    public static void main(String[] args) {
        //묵시적 형변환: 작은집 -> 큰집
        Person person = new SpiderMan("스파이더 맨",20);
        person.spiderJump(); //사용 불가
        
        //명시적 형변환: 큰집 -> 작은집
        SpiderMan sman = (SpiderMan)person;
        sman.spiderJump();   
    }
}

작은집에서 큰집으로 이동하는 묵시적 형변환은 값이 잘릴 염려가 없지만, 명시적 형변환은 큰집에서 작은집으로 옮겨가므로 반드시 명시적 표현이 필요하다. 

Person person = new Person();
SpiderMan sman = (SpiderMan)person;

위 코드는 문법적으로 문제가 없다. 하지만, 여기서 메모리에 있는 객체는 Person 타입이다. 그러다 보니 SpiderMan이 되기 위한 충분조건을 갖추지 못한 상태다. Person은 Object 객체와 Person 객체를 가지고 있을뿐, SpiderMan 객체의 메서드를 참조하고 있지는 않다. 

 

결론적으로부모 타입을 언제나 형변환 연산자를 통해 자식 타입으로 변경할 수 없는 것이다. 먼저 메모리에 있는 객체가 형변환을 위한 충분 조건을 가지고 있는지 확인하는 작업이 필요하다. 이 때 사용하는 연산자가 instanceof다.

 

instanceof

 

실제 메모리에 있는 객체가 특정 클래스 타입인지를 boolean 타입으로 리턴한다. 이 결과가 true로 반환되면 그 때 형변환 처리를 하는 것이다. 

Person person = new Person();
if(person instanceof SpiderMan){
	SpiderMan sman = (SpiderMan)person;
}