다형성이란 객체 지향 프로그래밍의 특징 중 하나로, 하나의 메소드나 클래스로 다양한 결과를 만드는 것을 말한다.

다형성의 예로는 Overloading(오버로딩), Inheritance(상속), Interface(인터페이스) 등이 있다.

Overloading

오버로딩은 지난번 글 오버라이딩과 오버로딩에서 설명하기도 했다.

오버로딩이 다형성의 예는 아니라는 말도 존재하긴 한다.

System.out.println은 본질적으로 출력한다는 결과는 같을 수 있지만, 매개변수의 타입에 따라 코드의 동작은 다르다.
같은 이름이지만 다른 동작을 갖는 이러한 성질이 다형성의 한 예라고 볼 수 있다.

Inheritance

오버라이딩도 메소드 하나로 상속한 클래스마다 다른 결과가 나타나기 때문에 다형성의 예로 볼 수 있다.
이 외에 상속과 관련해 객체를 생성할 때도 다형성을 확인할 수 있는데, 상속과 오버라이딩으로 인해 가능한 특징 중 하나이다.

아래 코드를 한번 살펴보자.

Object Type Conversion

class A{
    public String className(){
        return "A";
    }
}
class B extends A{
    @Override
    public String className(){
        return "B";
    }
    public void printclassName(){
        System.out.println("B");
    }
}
public class Example{
    public static void main(String[] args){
        A object = new B();

        System.out.println(object.className());//결과: B
        object.printclassName();//결과: 컴파일 에러
    }
}

보통 우리는 객체를 생성할 때 A object = new A(); 와 같이 생성한다.
그런데 위의 코드는 조금 다르다. A 타입 변수에 B 객체를 생성해주었다.

이렇게 객체를 생성하게 되면 object는 A 클래스에 존재하는 메소드와 필드만 사용할 수 있다.
그러나 오버라이딩한 메소드는 B 클래스에 존재하는 메소드 형태로 사용이 가능하다.
예를 들어 위 코드에서 object.className를 실행했을 때 B가 반환되고, B 클래스에 존재하는 printclassName() 컴파일 에러가 발생해 사용할 수 없다.

이런 방식의 객체 생성이 과연 어디에 쓰일까 하는 생각이 들 수 있다.
아래 코드를 보면 어느정도 이해가 갈 것이라고 생각한다.

class BaseClass{
    public String className(){
        return "BaseClass";
    }
}
class B extends BaseClass{
    @Override
    public String className(){
        return "B";
    }
    ...
}
class C extends BaseClass{
    @Override
    public String className(){
        return "C";
    }
}
public class Example{
    public static void printClassName(BaseClass A){
        System.out.println(A.className());
    }
    public static void main(String[] args){
        BaseClass b = new B();
        BaseClass c = new C();

        printClassName(b);//출력결과 B
        printClassName(c);//출력결과 C
    }
}

만약 printClassName의 매개변수 타입이 BaseClass가 아니라면, 위와 같은 처리는 불가능할 것이다.
만약 BaseClass를 배열로 생성한다면 printClassName과 같은 함수도 한꺼번에 처리할 수 있다.

이렇게 하나의 클래스(BaseClass)가 B, C라는 다양한 결과를 만들어 내므로 이도 다형성의 예로 볼 수 있다.

Interface

오버라이딩과 비슷하게, 하나의 인터페이스 클래스로 다양한 클래스라는 결과를 만들어낼 수 있으므로 이 또한 다형성의 예로 들 수 있다.

이 외에 인터페이스도 위의 예인 클래스 타입 변환처럼 사용할 수 있다.

아래의 코드를 한번 살펴보자.

interface ABC{
    public String ABC();
}
interface DEF{
    public String DEF();
}
class Test implements ABC, DEF{
    public String ABC(){
        return "ABC";
    }
    public String DEF(){
        return "DEF";
    }
}
public class Example{
    public static void main(String[] args){
        Test testing = new Test();
        ABC testing2 = new Test();
        DEF testing3 = new Test();

        testing.ABC();
        testing.DEF();

        testing2.ABC();
        testing2.DEF();//컴파일 에러

        testing3.ABC();//컴파일 에러
        testing3.DEF();
    }
}

앞선 예제에서 부모 클래스 타입으로 자식 클래스를 생성했듯이, 인터페이스 타입으로 해당 인터페이스를 참조한 클래스를 생성할 수 있다.

이를 통해 해당 인터페이스에 존재하는 메소드만 사용할 수 있도록 제한이 가능해진다.
이렇게 하나의 클래스가 인터페이스를 활용해 제한되는 성질도 다형성의 예로 볼 수 있다.


다형성을 한 문장으로 설명하라면 항상 어렵다. 이와 관련된 개념들이 머릿 속에 둥둥 떠다니는 것 같다.
오늘 정리를 통해서 좀 더 기억에 오래 남았으면 좋겠다.

다형성이란 하나로 여러 가지 결과를 만들 수 있는 것을 뜻한다는 것을 꼭 기억하자!