Java

Java(7) 다형성

hyomee2 2024. 8. 8. 13:58

1. 다형성(Polymorphism)이란?

: 하나의 인스턴스 여러가지 타입을 가질 수 있는것을 의미한다.

- 하나의 타입으로 여러 타입의 인스턴스를 처리할 수 있고, 하나의 메소드 호출로 객체 별로 각기 다른 방법으로 동작하게 할 수도 있다.

 

* cf. 객체지향 프로그래밍의 3대 특징: 1) 캡슐화 2) 상속 3) 다형성

 

 

2. 다형성의 장점

1) 여러 타입의 객체를 하나의 타입으로 관리 가능 -> 유지보수성 & 생산성 증가

Car[] carr = new Car[5];
carr[0] = new Sonata();
carr[1] = new Morning();
carr[2] = new Avante();
carr[3] = new Grandure();
carr[4] = new Porter();

for(Car car : carr) {
    car.move();
}
// 서로 다른 종류의 차들이지만 Car type으로 관리 가능

 

2) 상속 기반 기술이기 때문에 상속관계에 있는 모든 객체는 동일한 메세지를 수신할 수 있다.

또, 동일한 메세지를 객체 별로 다르게 할 수도 있다. (다양한 기능 사용에 있어서 관리해야 하는 메세지 종류가 줄어든다.)

carr[0].moveSonata();
carr[1].moveMorning();
carr[2].moveAvante();
carr[3].moveGrandure();
carr[4].movePorter();

for(Car car : carr) {
    car.move();
}

 

3) 확장성이 좋은 코드를 작성할 수 있다.

move(new Sonata());
move(new Morning());
/* 새로운 차량 추가 시 move 메소드를 더 만들지 않아도 된다. */
move(new Santafe());

public void move(Car car) {
    car.move();
}

 

4) 결합도를 낮춰서 유지보수성을 증가시킬 수 있다.

// 결제수단을 현금만 사용하는 경우
public void pay(현금) {
    현금.결제();
}

// 결제수단이 카드로 변경되는 경우
public void pay(카드) {
    카드.결제();
}

// 카드결제 혹은 현금결제 둘 다 가능하도록 어느 한 쪽을 의존하지 않게 만듦
public void pay(결제수단) {
    결제수단.결제();
}

 

 

3. 동적 바인딩

: 컴파일 당시에는 해당 타입의 메소드와 연결되어 있다가 런타임 시 실제 해당 인스턴스가 오버라이딩한 메소드로 바인딩이 바뀌어 동작하는 것

(1) 동적 바인딩의 성립 조건

- 상속 관계를 갖는 부모 자식 클래스에 오버라이딩 된 메소드를 호출해야 한다.

 

 

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

- 상속 관계에 있지만 오버라이딩한 것이 아닌 후손 객체가 고유하게 갖는 확장된 기능 사용 위해선 실제 인스턴스의 타입으로 다운캐스팅을 해주어야 한다.

- 상위 타입 형변환(업캐스팅)은 묵시적으로 일어나지만 하위 타입 형변환(다운캐스팅)은 명시적으로 작성해야 한다.

 

 

5. instanceof 연산자

: 레퍼런스 변수가 실제로 어떤 클래스 타입의 인스턴스인지 확인하여 true/false를 반환

- 타입 형변환을 잘못하는 경우 ClassCastException이 발생하므로 안전한 형변환을 위해 instanceof 연산자를 이용할 수 있다.

If(레퍼런스 변수 instanceof 클래스 타입) {
    // true일 때 처리할 내용, 해당 클래스 타입으로 down-casting할 것
}

 

 

<Animal, Rabbit, Tiger 클래스 정의>

public class Animal {
    public void eat() {
        System.out.println("동물이 먹이를 먹습니다.");
    }
    public void run() {
        System.out.println("동물이 달려갑니다.");
    }
    public void cry() {
        System.out.println("동물이 울음소리를 냅니다.");
    }
}


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("토끼가 점프합니다~! 점프~!");
    }
}


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("호랑이가 물어 뜯습니다 앙");
    }
}

 

<main 메소드>

 

public class Application {
    public static void main(String[] args) {
        // Rabbit과 Tiger는 Animal이기도 하다.
        // 부모 타입의 레퍼런스 변수로 자식 인스턴스의 주소 값 참조 가능 (업캐스팅)
        Animal a1 = new Rabbit();
        Animal a2 = new Tiger();

        // 자식 타입의 레퍼런스 변수로 부모 타입의 인스턴스 주소 값 참조는 불가
        // Rabbit r1 = new Animal();
        // Tiger t1 = new Animal();

        // 컴파일 당시에는 해당 타입의 메소드와 연결 되어 있다가 (정적 바인딩)
        // 런타임 당시 실제 객체가 가진 오버라이딩 된 메소드로 바인딩이 바뀌어 동작 (동적 바인딩)
        a1.cry();
        a2.cry();

        // 현재 레퍼런스 변수 타입은 Animal이기 때문에 Rabbit, Tiger의 메소드를 호출 불가
        // a1.jump();
        // a2.bite();

        // 형변환을 통해 호출 (다운캐스팅)
        ((Rabbit)a1).jump();
        ((Tiger)a2).bite();

        // 타입 형변환을 잘못 하는 경우 ClassCastException 발생
        // ((Tiger)a1).bite();

        // instanceof: 레퍼런스 변수가 참조하는 실제 인스턴스가 원하는 타입과 맞는지 비교
        System.out.println("a1이 Rabbit 타입인지 확인 : " + (a1 instanceof Rabbit));
        System.out.println("a1이 Tiger 타입인지 확인 : " + (a1 instanceof Tiger));
        System.out.println("a2이 Rabbit 타입인지 확인 : " + (a2 instanceof Rabbit));
        System.out.println("a2이 Tiger 타입인지 확인 : " + (a2 instanceof Tiger));
        
        /* 상속 받은 타입도 가지고 있다. */
        System.out.println("a1이 Animal 타입인지 확인 : " + (a1 instanceof Animal));
        System.out.println("a2이 Animal 타입인지 확인 : " + (a2 instanceof Animal));
        /* 모든 클래스는 Object의 후손이다. */
        System.out.println("a1이 Object 타입인지 확인 : " + (a1 instanceof Object));
        System.out.println("a2이 Object 타입인지 확인 : " + (a2 instanceof Object));

        /* 해당 타입이 맞는 경우에만 클래스 형변환을 수행 */
        if(a1 instanceof Rabbit) ((Rabbit)a1).jump();

        if(a2 instanceof Tiger) ((Tiger)a2).bite();

        /* up-casting : 상위 타입으로 형변환(묵시적, 자동 형변환)
        * down-casting : 하위 타입으로 형변환(명시적, 강제 형변환)
        * */

        Animal animal1 = (Animal) new Rabbit();
        Animal animal2 = new Rabbit();

        Rabbit rabbit1 = (Rabbit) animal1;
//        Rabbit rabbit2 = animal1;


    }
}