본문 바로가기

Java/문법 및 이해

[JAVA] 다형성 Polymorphism

1. 다형성(Polymorphism)

다형성이라는 개념은 객체 지향 프로그래밍에서 아주 중요한 개념이다. 당연히 자바에서도 굉장히 중요하게 여기고 있고, 다형성을 모른다면 객체 지향 프로그래밍을 쓸 수 없을 만큼 치명적이다.

 

아래의 코드는 People라는 클래스이다. 클래스 안에는 printInfo라는 메서드를 작성했다.

class People {
    public void printInfo() {
        System.out.println("사람");
    }
}

public class StringTest {
    public static void main(String[] args) {
        People p = new People();
        p.printInfo();
    }
}

위 코드 결과는 당연히 "사람" 일 것이다.

 

그렇다면 Man과 Woman 클래스를 추가해서 People 클래스를 상속해보자.

 

class People {
    public void printInfo() {
        System.out.println("사람");
    }
}

class Man extends People {
    public void printInfo() {
        System.out.println("남자");
    }
}
class Woman extends People {
    public void printInfo() {
        System.out.println("여자");
    }
}

public class StringTest {
    public static void main(String[] args) {
        People p = new People();
        p.printInfo();
        Man m = new Man();
        m.printInfo();
        Woman w = new Woman();
        w.printInfo();
    }
}

extends를 통해 People 클래스를 상속 받고 실행해보면..

사람
남자
여자

그 이유는 m은 Man() 생성자를 호출하였고, w는 Woman 생성자를 호출하였기 때문이다.

 

UML 다이어그램을 본다면 위와 같다.

바로 Man과 Woman은 People를 상속하기 때문이다.

 

is-a 관계로 해석해보면

Man은 People이다. (남자는 사람이다.)

Woman은 People이다. (여자는 사람이다.)

이러한 관점으로 볼 수 있다.

 

 

하지만 여기서 중요한 점은 Man은 People로 표현할 수 있고, Woman도 People로 표현할 수 있다는 것이다.

여기서 다형성 개념이 나오게 된다. Man과 Woman은 People이기 때문에 People라는 자료형을 받을 수 있다.

 

확인해보자.

class People {
    public void printInfo() {
        System.out.println("사람");
    }
}

class Man extends People {
}
class Woman extends People {
}

public class StringTest {
    public static void main(String[] args) {
        People p = new People();
        p.printInfo();
        People m = new Man();
        m.printInfo();
        People w = new Woman();
        w.printInfo();
    }
}
사람
사람
사람

Man과 Woman은 분명 printInfo() 메서드가 없는데도 에러 없이 정상 출력이 되었다. 

그 이유는 People 부모 클래스의 printInfo() 메서드를 물려받아 호출한 것이다.

 

즉, 자식 클래스는 부모 클래스로 받을 수 있다는 점을 우리는 꼭 기억해야한다. 실제로도 부모 클래스를 객체 변수로 받는 경우를 많이 사용한다고 한다.

 

 

 

2. 다형성과 오버라이딩(Overriding)

Man과 Woman은 printInfo를 물려받았고 오버라이딩(Overriding)을 할 수 있다.

코드를 다음과 같이 수정해보자.

class People {
    public void printInfo() {
        System.out.println("사람");
    }
}

class Man extends People {
    @Override
    public void printInfo() {
        super.printInfo();
        System.out.println("남자");
    }
}
class Woman extends People {
    @Override
    public void printInfo() {
        super.printInfo();
        System.out.println("여자");
    }
}

public class StringTest {
    public static void main(String[] args) {
        People p = new People();
        p.printInfo();
        People m = new Man();
        m.printInfo();
        People w = new Woman();
        w.printInfo();
    }
}
사람
사람
남자
사람
여자

위의 결과처럼 오버라이딩된 printInfo()를 호출한다는 것을 알 수 있다.

즉, 다형성에서 People은 자식클래스에서 재정의된 메서드를 호출할 수 있다는 것이다.

 

그렇다면 Woman과 Man에서 단독으로 정의한 메서드는 어떻게 될까?

다음과 같이 추가해보자.

class Man extends People {
    @Override
    public void printInfo() {
        super.printInfo();
        System.out.println("남자");
    }
    
    public void enlist() {
        System.out.println("남자 맞음;;");
    }
}
class Woman extends People {
    @Override
    public void printInfo() {
        super.printInfo();
        System.out.println("여자");
    }
    
    public void makeUp() {
        System.out.println("여자 맞음;;");
    }
}

실행 결과... p.enlist를 호출하려한다면 호출이 되지 않는다. 왜냐하면 People 자료형이기 때문이다. People는 enlist()라는 메서드를 갖고있지 않기 때문이다.

 

이런 경우에는 아래의 코드처럼 데이터 형에 맞게 캐스팅해주어야 한다.

public class StringTest {
    public static void main(String[] args) {
        People p = new People();
        p.printInfo();
        People m = new Man();
        m.printInfo();
        ((Man) m).enlist();
        People w = new Woman();
        w.printInfo();
        ((Woman) w).makeUp();
    }
}
사람
사람
남자
남자 맞음;;
사람
여자
여자 맞음;;

왜 이렇게 해야할까?

People는 자신을 상속한 클래스 중에서 어떤 메서드를 만들지, 어떤 멤버 변수를 만들어낼지 알아낼 수 없기 때문이다.

때문에 그 메서드가 있는 객체로 직접 캐스팅 즉, 형변환을 해주어서 메서드를 사용해야한다.

 

보기에는 간단해보이지만, 실제로 적용해보면 굉장히 복잡하고 난해하다.

그러면 이러한 다형성은 어디에 쓰일까?

 

대표적으로 메서드에서 매개변수로 People을 상속하는 클래스를 받을 때 사용할 수 있다.

public class StringTest {
    public static void func(People people) {
        people.printInfo();
    }
    public static void main(String[] args) {
        Man man = new Man();
        Woman woman = new Woman();

        func(man);
        func(woman);
    }
}

func 의 매개변수 people는 People의 객체이기 때문에 그것을 상속하는 모든 클래스를 받아낼 수 있다.

그래서 Object 객체로 모든 객체를 받을 수 있는 것도 바로 이러한 다형성의 속성 때문이다.

 

또한, 필요에 의해서는 instanceof 연산자를 사용해서 캐스팅할 수도 있다.

public class StringTest {
    public static void func(People people) {
        people.printInfo();

        if (people instanceof Man) ((Man) people).enlist();
        if (people instanceof Woman) ((Woman) people).makeUp();
    }
    public static void main(String[] args) {
        Man man = new Man();
        Woman woman = new Woman();

        func(man);
        func(woman);
    }
}
사람
남자
남자 맞음;;
사람
여자
여자 맞음;;

 

 

캐스팅에 대해서는 추후에 정리할 것이다.

 

 

3. 다형성과 오버로딩(Overloading)

class Man extends People {
    @Override
    public void printInfo() {
        System.out.println("남자");
    }

    public void enlist() {
        System.out.println("남자 맞음;;");
    }

    public void printData() {
        System.out.println("디폴트 데이터");
    }
    public void printDate(int i) {
        System.out.println("정수 데이터");
    }
    public void printData(String s) {
        System.out.println("문자열 데이터");
    }
}
class Woman extends People {
    @Override
    public void printInfo() {
        super.printInfo();
        System.out.println("여자");
    }

    public void makeUp() {
        System.out.println("여자 맞음;;");
    }
}

public class StringTest {
    public static void func(People people) {
        people.printInfo();

        if (people instanceof Man) ((Man) people).enlist();
        if (people instanceof Woman) ((Woman) people).makeUp();
    }
    public static void main(String[] args) {
        Man man = new Man();
        Woman woman = new Woman();

        man.printData();
        man.printDate(123);
        man.printData("Sss");
    }
}
디폴트 데이터
정수 데이터
문자열 데이터

Man 클래스 내에 오버로딩 메서드를 3개 추가하였다.

결과에서 보이는 것 처럼 같은 이름의 메서드라도 매개변수 타입에 따라 다른 메서드를 출력하는 것을 볼 수 있다.

 

 

 

 

 

 

[참고]

https://reakwon.tistory.com/48