자바 다형성 | 자바 입문강좌 29

자바 다형성

자바 다형성에 대하여 알아보겠습니다. 다형성(polymorphism)은 객체지향 프로그래밍의 중요한 원리로 하나의 코드를 다양한 자료형으로 실행하는 것을 뜻합니다.

좀 tricky 한데 코드로 살펴보겠습니다.

이전 학습에서 가상 메소드 테이블를 알아봤습니다. 가상 메소드 테이블은 sub 클래스에서 오버라이딩한 경우 코드가 달라집니다. 하나의 클래스에 하나의 메소드 코드만 필요하다는 이야기를 했습니다 오버라이딩 여부에 따라 두개 이상이 될 수 있습니다.

아래의 스니펫은 하나의 base 클래스에 세개의 sub 클래스가 각각 오버라이딩을 하는 경우입니다. 이런 경우 가상 메소드 테이블이 가리키는 메소드의 개수는 4개가 돼겠죠.

package com.kay;

public class Main {

    public static void main(String[] args) {
        Main bs1 = new Main();

        bs1.callSub(new BaseA());
        bs1.callSub(new SubA());
        bs1.callSub(new SubB());
        bs1.callSub(new SubC());
    }
    public void callSub(BaseA bs){
        bs.showInfo();
    }
}


class BaseA{
    public void showInfo(){
        System.out.println("Base A");
    }
}
class SubA extends BaseA{
    public void showInfo(){
        System.out.println("Sub A");
    }
}
class SubB extends BaseA{
    public void showInfo(){
        System.out.println("Sub B");
    }
}
class SubC extends BaseA{
    public void showInfo(){
        System.out.println("Sub C");
    }
}
Base A
Sub A
Sub B
Sub C

main 함수에서 사용하는 방식이 눈길을 끕니다. 일단 자신이 속한 클래스인 Main으로 선언을 합니다. Main 에는 callSub 라는 이름으로 Base 클래스의 참조변수를 인수로 받습니다. 이 방식을 사용하면 인스턴스의 종류는 바뀌고 참조변수는 Base 클래스 하나로 연결이 됩니다.

bs1.callSub 에서 (new BaseA())는 new BaseA 가 인스턴스를 반환하여 callSub 의 인수로 들어가는데 Base 클래스 변수에 4개의 다른 클래스를 매칭시킵니다.

결과의 의미는 여러개의 하위 클래스를 관리하기 위해 하나의 Base 클래스 변수를 사용하는 것입니다. 즉 클래스마다 각각의 변수를 선언할 필요가 없습니다.

반복해서 언급하지만 클래스의 원리를 잘 이해하려면 머리속에 메모리 모델이 있어야 합니다. 처음부터 지엽적 사실에 집착하기 보다는 자연스럽게 메모리를 떠올려 보세요.

조작하기 위한 클래스는 1개를 선언했습니다. 그것도 callSub 메소드로 우회하였습니다. 결국 이게 의미하는 것은 BaseA 타입을 사용해서 조작한다는 뜻 입니다. callSub 를 네번 호출할 시 4개의 인스턴스가 생성됩니다. 3개의 sub 클래스를 오버라이드 했기 때문에 가상 메소드 테이블에는 4개의 메소드 주소가 등록될 것 입니다.

자바는 GC(Garbage Collection)을 실행하기 때문에 처음에는 메모리에 무언가 만드는 것만 신경써도 프로그램이 fail 할 염려는 업습니다.

다형성이라고 이야기하지만 사실은 메모리를 어떻게 연결하느냐의 문제로 볼 수 있습니다.

그래서 완벽하지 않더라도 나름의 메모리의 모델을 머리속에 그린다면 좋습니다. 타이핑을 치는 것도 중요하지만 치면서 이해하지 못하면 한계가 있습니다.

하나의 코드를 완전히 이해한다는 마음으로 실습을 진행하는 것이 좋습니다.

다형성 ArrayList 예제

다음은 다형성 ArrayList 예제입니다.

ArrayList 는 콜렉션 프레임워크의 컨테이너 입니다. 객체를 배열의 형식으로 담을 수 있습니다.

아래 코드는 다형성을 활용하여 인스턴스 4개를 만든 후 이것들의 참조를 ArrayList 하나에 전부 넣어 버립니다. 이제 ArrayList 하나로 전체 인스턴스를 관리할 수 있습니다.

package com.kay;

import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        
        ArrayList<BaseA> bsLst = new ArrayList<BaseA>();

        BaseA bc1 = new BaseA();
        BaseA bc2 = new SubA();
        BaseA bc3 = new SubB();
        BaseA bc4 = new SubC();

        bsLst.add(bc1);
        bsLst.add(bc2);
        bsLst.add(bc3);
        bsLst.add(bc4);

        System.out.println("[ArrayList를 사용한 다형성 예제]");

        for(BaseA bs : bsLst) {
            bs.showInfo();
        }
    }
}

class BaseA{
    public void showInfo(){
        System.out.println("- Base A");
    }
}
class SubA extends BaseA{
    public void showInfo(){
        System.out.println("- Sub  A");
    }
}
class SubB extends BaseA{
    public void showInfo(){
        System.out.println("- Sub  B");
    }
}
class SubC extends BaseA{
    public void showInfo(){
        System.out.println("- Sub  C");
    }
}
[ArrayList를 사용한 다형성 예제]
- Base A
- Sub  A
- Sub  B
- Sub  C

이전의 코드를 이해했다면 어렵지 않습니다. 향상된 for 문 사용방식은 요즘 동적 언어들(파이썬 등)에서 쉽게 볼 수 있는 방식이죠. 직관적으로 ArrayList 에 있는 요소들을 하나씩 꺼내와서 실행하고 요소를 끝까지 반복하면(Iterate) 종료합니다.

상속의 관계 HAS-A 와 IS-A

상속이 다형성의 다양한 설계 유연성을 제공함에도 불구하고 무분별한 상속의 사용은 권장하지 않습니다. 객체 지향 프로그래밍에서는 HAS-A와 IS-A관계를 나누는데요. HAS-A는 소속 관계이며 예를 들어 ‘직원은 월급을 받는다’란 것 처럼 월급 속성이 있습니다. 월급은 객체이고 클래스의 멤버로 적합합니다. IS-A관계는 ‘사람은 동물이다.’ 라는 명제 처럼 동물 클래스에 사람이 속해있기 때문에 class 사람 extends 동물 과 같이 상속관계(확장)가 의미 있습니다.

물론 마음대로 상속하는 설계도 가능합니다.

class 텔레비전 extends 라디오

처럼 만들어도 안될 건 없습니다. 프로그램은 돌아가고요. 이것을 너무 인문학적으로 받아들이는 사고는 좋지않습니다.

기술적으로 보면 HAS-A는 멤버 변수의 관계고, IS-A는 생성자 부터 메소드까지 포괄해서 확장하는 관계입니다.

이런 개념을 결정하는 것이 생각보다 조물주의 영역처럼 느껴지고 어렵기 때문에 객체 지향 프로그래밍이 더 어렵게 느껴집니다. 처음에는 기술적으로 충분히 접근하고 나중에 설계가 가능한 단계가 되면 머리속에서 개념 정리를 해두는게 좋습니다.

업계에서 공식적으로 통용되는 설계라는 것도 있기 때문에 오픈소스 들을 보면서 공부하는 것도 좋은 방법입니다. 아 물론 처음에는 Java API를 교과서로 삼는 것이 좋은 방법입니다. 자바의 역사가 꽤 오래되었기 때문에 지금은 deprecated (사용중지)된 클래스나 메소드 등이 꽤 있습니다. 그런 것들도 하나하나 다 탄생과 종말의 스토리가 있고 자바의 아이디어가 진화한 흔적입니다.

instanceof 키워드

다형성을 사용하다 보면 한가지 문제가 base 클래스 참조변수인데 어느 sub 클래스의 인스턴스인지 기억이 안나거나 헷갈릴 때가 있습니다.

이때를 위해 instanceof 키워드를 사용하여 boolean 으로 검증할 수 있습니다.

주의할 점은 모든 sub클래스는 base 클래스의 인스턴스라는 점입니다. 포함관계를 생각하면 각 sub 클래스는 서로 다른 인스턴스입니다.

타입 다운 캐스팅 시 인스턴스 타입을 제대로 파악한 후 하도록 합니다. 타입 캐스팅은 런타임 에러가 있기 때문에 instanceof 키워드로 오류가능성을 미리 차단하는게 좋습니다.

public class Main {

    public static void main(String[] args) {


        BaseA bc1 = new BaseA();
        BaseA bc2 = new SubA();
        BaseA bc3 = new SubB();
        BaseA bc4 = new SubC();
        
        System.out.println("[instanceof 키워드]");
//        BaseA
        System.out.println(bc1 instanceof BaseA);
        System.out.println(bc1 instanceof SubA);
//        SubA
        System.out.println(bc2 instanceof SubA);
        System.out.println(bc2 instanceof BaseA);

//        SubB
        System.out.println(bc3 instanceof SubB);
        System.out.println(bc3 instanceof BaseA);

//        SubC
        System.out.println(bc4 instanceof SubC);
        System.out.println(bc4 instanceof BaseA);

//        타입캐스팅 예시
        if (bc2 instanceof SubA){
            SubA sb1 = (SubA) bc2;
            System.out.println("타입 캐스팅 성공");
        }
    }
}
[instanceof 키워드]
true
false
true
true
true
true
true
true

요약

자바 다형성에 대하여 다소 부족하지만 다루어 봤습니다.

처음 접했을 때 어렵게 느껴진다면 당연한 것 이고요. 머리속에 메모리에 대한 모델을 가지고 있어야 이리 굴렸다 저리 굴렸다가 쉽습니다.

한편으로 C++ 에 비하면 자바의 OOP는 쉬운편이기 때문에 초보자도 무리없이 이해할 수 있을 것 입니다. 객체 지향 프로그래밍의 길은 멀기 때문에 너무 급하게 생각하지 말고 한걸음씩 학습하고 작은 내용도 자기 것으로 만드는 것에 집중하는 것을 추천합니다.

외부참고문서

Java Polymorphism (w3schools.com)

다형성 – Java (opentutorials.org)

Java – Polymorphism – 자바 다형성 Tutorialspoint

자바 다형성, ArrayList 배열의 활용 자바 강좌 8-4

Leave a Comment