한화시스템/백엔드

[BE] JAVA_다형성

jjam-mo 2024. 7. 17. 19:36

1. 다형성의 정의 및 사용 이유

1-1. 다형성(Polymorphism)이란?

→ 타입 은닉

💡 하나의 인스턴스가 여러 가지 타입을 가질 수 있는 것을 의미한다.
하나의 타입으로 여러 타입의 인스턴스를 처리할 수 있기도 하고, 하나의 메소드 호출로 객체별로 각기 다른 방법으로 동작하게 할 수도 있다.

1-2. 다형성의 장점

  1. 유지보수성과 생산성 증가(여러 타입의 객체를 하나의 타입으로 관리)
  2. 상속관계에 있는 모든 객체는 동일한 메세지를 수신. 동잃한 메세지를 수신받아 처리하는 내용을 객체별로 다르게 할 수 있다
  3. 확장성이 좋은 코드 작성
  4. 결합도 낮춰서 유지보수성 증가

1-3. 동적 바인딩

💡 컴파일 당시에는 해당 타입의 메소드와 연결되어 있다가 런타임 시 실제 해당 인스턴스가 메소드(오버라이딩 한 메소드)로 바인딩이 바뀌어 동작하는 것
💡 동적 바인딩의 성립 조건
: 상속 관계를 가지는 부모 자식 클래스에 오버라이딩 된 메소드를 호출해야 한다.

1-4. 업캐스팅과 다운캐스팅

💡 상속관계에 있지만 오버라이딩 한 것이 아닌 후손 객체가 고유하게 가지는 확장된 기능을 사용하기 위해서는 실제 인스턴스으 ㅣ타입으로 다운캐스팅(클래스 형변환)을 해주어야 한다.
  • 클래스 형변환
    • 상위 타입 형변환(up-casting) : 묵시적 형변환
    • 하위 타입 형변환(down-casting) : 명시적 형변환

1-5. instanceof 연산자

💡 클래스 형변환의 경우 런타임시 존재하는 타입과 형변환하려는 타입이 일치하지 않는 경우 ClassCastException이 발생.
런타임 시 안전한 형변환을 하기 위해 instanceof 연산자 이용.
instanceof 연산자는 레퍼런스 변수가 실제로 어떤 클래스 타입의 인스턴스인지 확인하여 true or false를 반환

 

 

< 전체 예시 >

package com.ohgiraffers.section01.polymorphism;

public class Application1 {
    public static void main(String[] args) {

        /* 수업목표. 다형성과 타입 형변환에 대해 이해할 수 있다. */
        Animal animal = new Animal();
        animal.eat();
        animal.run();
        animal.cry();

        System.out.println();

        Tiger tiger = new Tiger();
        tiger.eat();
        tiger.run();
        tiger.cry();
        tiger.bite();

        System.out.println();

        Rabbit rabbit = new Rabbit();
        rabbit.eat();
        rabbit.run();
        rabbit.cry();
        rabbit.jump();

        /* 설명. 다형성 적용(부모 타입으로 자식 인스턴스의 주소값 저장) */
        Animal an1 = new Animal();      // 다형성 X
        Animal an2 = new Tiger();       // 다형성 O, 자동형변환
                                        // 순서 안헷갈리게 이해하자!
        Animal an3 = new Rabbit();      // 다형성 O
        // 다른 타입의 객체를 배열로 다룰 수 있겠구나!! (영상 다시 봐야할듯ㅜㅜ)

        /* 설명. Animal은 Tiger나 Rabbit이 아니다.(Animal Tiger나 Rabbit 타입을 지니고 있지 않다. */
//        Tiger tiger1 = new Animal();     // 다형성 X

        /* 설명. 동적바인딩 확인하기 */
        /* 필기.
        *   컴파일 당시에는 해당 타입의 메소드와 연결되어 있다가
        *   런타임 당시 실제 객체가 가진 오버라이딩 된 메소드로 바인딩이 배뀌어 동작하는 것을 의미한다.
        *   (동적 바인딩의 조건: 1. 상속, 2. 오버라이딩)
        * */
        System.out.println("==== 동적 바인딩 확인하기 ====");
        an1.cry();
        an2.cry();
//        an2.bite();     // 컴파일 시점에서 an2는 아직 객체가 생성이 안되서 해당 메소드 오류
        an3.cry();

        /* 설명. 부모타입을 자식 타입으로 강제 형변환 하는 것은 가능하다.(조심해야 한다.) */
//        ((Tiger)an3).cry();     // 컴파일 시점에 에러가 발샏하지 않고 런타임 시점에야 에러가 발생한다.

        /* 설명. 오버라이딩 되지 않은(추가한 메소드) 메소드 호출은 다운캐스팅을 해 주어야 한다. */
        /* 필기.
        *   instanceof란?
        *    해당 객체의 타입을 런타임 시점에 확인하기 위한 연산자
        * */
        if (an3 instanceof Tiger) {     // 실행 안함!
            ((Tiger)an3).bite();
        }

        if (an3 instanceof Rabbit) {
            ((Rabbit)an3).jump();       // 한 번 확인하고 써야한다. 강제형변환으로 가능해진다.
            System.out.println("an3는 토끼지");
        }

        if (an3 instanceof Animal) {
            System.out.println("Animal은 맞지");
        }

        /* 설명. 다형성은 상속관계의 객체들간에 형변환 */
        Animal animal2 = new Tiger();       // 다형성을 적용, 자동형변환(auto up-casting), 묵시적 형변화
        Rabbit rabbit2 = (Rabbit)an3;       // 다형성이 적용되지 않음, 강제형변환(down-casting), 명시적 형변환
    }
}

// 실행 결과
동물이 먹이를 먹습니다.
동물이 달려갑니다.
동물이 울음소리를 냅니다.

호랑이가 고기를 뜯어 먹습니다.
호랑이는 왠만해선 달리지 않습니다. 어슬렁~ 어슬렁~ 걸어갑니다.
호랑이가 울부짖습니다. 어흥!~~~~
호랑이가 물어뜯습니다. 앙!~

토끼가 풀을 뜯어 먹고 있습니다.
토끼가 달려갑니다. 깡충~ 깡충~
토끼가 울음소리를 냅니다. 끼익~! 끼익~!
토끼가 점프합니다. 점프!!
==== 동적 바인딩 확인하기 ====
동물이 울음소리를 냅니다.
호랑이가 울부짖습니다. 어흥!~~~~
토끼가 울음소리를 냅니다. 끼익~! 끼익~!
토끼가 점프합니다. 점프!!
an3는 토끼지
Animal은 맞지
package com.ohgiraffers.section01.polymorphism;

public class Application2 {
    public static void main(String[] args) {

        /* 수업목표. 다형성을 적용하여 객체 배열을 만들어 다양한 인스턴스들을 연속 처리할 수 있다. */
        Animal[] animals = new Animal[5];
        animals[0] = new Rabbit();
        animals[1] = new Tiger();
        animals[2] = new Rabbit();
        animals[3] = new Tiger();
        animals[4] = new Rabbit();

//        for (int i = 0; i < animals.length; i++) {
//            animals[i].cry();       // cry 사용가능? 오버라이딩 되어있다.
//        }

        for(Animal animal : animals) {
            animal.cry();

            /* 설명. 오버라이딩 되지 않은 메소드는 추가 처리가 필요 */
                    if(animal instanceof Tiger) {       // 런타임 에러 방지 코드
                        ((Tiger)animal).bite();
                    }
                    if(animal instanceof Rabbit) {
                        ((Rabbit)animal).jump();
                    }
        }
    }
}

// 실행 결과
토끼가 울음소리를 냅니다. 끼익~! 끼익~!
토끼가 점프합니다. 점프!!
호랑이가 울부짖습니다. 어흥!~~~~
호랑이가 물어뜯습니다. 앙!~
토끼가 울음소리를 냅니다. 끼익~! 끼익~!
토끼가 점프합니다. 점프!!
호랑이가 울부짖습니다. 어흥!~~~~
호랑이가 물어뜯습니다. 앙!~
토끼가 울음소리를 냅니다. 끼익~! 끼익~!
토끼가 점프합니다. 점프!!
package com.ohgiraffers.section01.polymorphism;

public class Animal {
    public void eat() {
        System.out.println("동물이 먹이를 먹습니다.");
    }

    public void run() {
        System.out.println("동물이 달려갑니다.");
    }

    public void cry() {
        System.out.println("동물이 울음소리를 냅니다.");
    }
}
package com.ohgiraffers.section01.polymorphism;

public class Tiger extends Animal {
    @Override
    public void eat() {
        System.out.println("호랑이가 고기를 뜯어 먹습니다.");
    }

    @Override
    public void run() {
        System.out.println("호랑이는 왠만해선 달리지 않습니다. 어슬렁~ 어슬렁~ 걸어갑니다.");
    }

    @Override
    public void cry() {
        System.out.println("호랑이가 울부짖습니다. 어흥!~~~~");
    }

    public void bite() {
        System.out.println("호랑이가 물어뜯습니다. 앙!~");
    }
}
package com.ohgiraffers.section01.polymorphism;

public class Rabbit extends Animal {
    @Override
    public void eat() {
        System.out.println("토끼가 풀을 뜯어 먹고 있습니다.");
    }

    @Override
    public void run() {
        System.out.println("토끼가 달려갑니다. 깡충~ 깡충~");
    }

    @Override
    public void cry() {
        System.out.println("토끼가 울음소리를 냅니다. 끼익~! 끼익~!");
    }

    public void jump() {
        System.out.println("토끼가 점프합니다. 점프!!");
    }
}