언어(Language)/Java

[Java] 자바 다형성(Polymorphism) 개념 정리 및 활용

잇트루 2022. 9. 18. 00:00
반응형

다형성 (Polymorphism)

다형성이란 여러 개를 의미하는 poly와 형태 또는 실체를 의미하는 morphism의 결합어로, 하나의 객체가 여러 가지 형태를 가질 수 있는 것을 의미한다.

 

자바에서 다형성은 한 타입의 참조 변수를 통해 여러 타입의 객체를 참조할 수 있도록 하는 것이다. 즉, 상위 클래스 타입의 참조 변수를 통해서 하위 클래스의 객체를 참조할 수 있도록 허용하여 상위 클래스가 동일한 메시지로 하위 클래스들이 서로 다른 동작을 할 수 있도록 한다.

 

다형성을 활용하면, 부모 클래스가 자식 클래스의 동작 방식을 알 수 없어도 오버라이딩을 통해 자식 클래스에 접근할 수 있다.

 

다형성의 장점

  1. 유지보수 : 여러 객체를 하나의 타입으로 관리할 수 있어 유지보수가 용이하다.
  2. 재사용성 : 객체의 재사용이 쉬워 재사용성이 높아진다.
  3. 느슨한 결합 : 클래스 간의 의존성을 줄여 확장성은 높아지고 결합도는 낮아진다.

 

다형성의 조건

  1. 상위 클래스와 하위 클래스는 상속 관계여야 한다.
  2. 다형성이 보장되기 위해 오버라이딩(하위 클래스 메서드의 재정의)이 반드시 필요하다.
  3. 자식 클래스의 객체가 부모 클래스의 타입으로 형변환(업캐스팅)해야 한다.
class Human {
    public void info() {
        System.out.println("나는 사람입니다.");
    }
}

class Female extends Human {
    public void info() {
        System.out.println("나는 여자입니다.");
    }
}

class Male extends Human {
    public void info() {
        System.out.println("나는 남자입니다.");
    }
}

public class PolymorphismEx {
    public static void main(String[] args) {
        // 객체 타입과 참조변수 타입이 일치
        Human human = new Human();
        Female female = new Female();

        // 객체 타입과 참조변수 타입이 불일치
        Human male = new Male();

        human.info();
        female.info();
        male.info();
    }
}

Human male = new Male(); 구문과 같이 객체의 타입과 참조 변수의 타입을 불일치시킬 수 있다.

이 경우, 상위 클래스인 Human을 참조 변수의 타입으로 지정했기 때문에 참조 변수가 사용할 수 있는 멤버의 개수는 상위 클래스의 멤버의 수가 된다.

상위 클래스 타입의 참조 변수로 하위 클래스의 객체를 참조하는 다형성의 핵심적인 부분이다.

 

그러나, 하위 클래스 타입으로 상위 클래스의 객체를 참조하는 것은 허용되지 않는다.

public class PolymorphismEx {
    public static void main(String[] args) {
        // 객체 타입과 참조변수 타입이 일치
        Human human = new Human();

        // 객체 타입과 참조변수 타입이 불일치(다형성)
        Human female = new Female();
        Human male = new Male();

        // 하위 클래스 타입으로 상위 클래스 참조 불가능
        // Female female2 = new Human();

        human.info();
        female.info();
        male.info();
    }
}

이러한 이유는 실제 객체인 Human의 멤버 개수보다 참조 변수인 female2가 사용할 수 있는 멤버 개수가 더 많기 때문이다.

 

참조 변수의 형 변환

자바에서는 기본 자료형의 형 변환도 가능하지만, 참조 변수도 형 변환이 가능하다.

참조 변수의 형 변환은 사용할 수 있는 멤버의 개수를 조절하는 것이다.

 

형 변환을 위한 조건

  1. 서로 상속관계에 있는 상위 클래스 : 하위 클래스 사이에만 형 변환이 가능하다.
  2. 업 캐스팅(하위 클래스 타입에서 상위 클래스 타입으로 형 변환)은 형 변환 연산자를 생략할 수 있다.
  3. 다운 캐스팅(상위 클래스에서 하위 클래스 타입으로 변환)은 형 변환 연산자를 반드시 작성해야 한다.
public class MainTest {
    public static void main(String[] args) {
        Child1 child1 = new Child1();

        // 업 캐스팅 () 생략 가능
        Parents parents = (Parents) child1;

        // 다운 캐스팅 () 생략 불가능
        Child1 child11 = (Child1) parents;

        // Child1과 Child2는 상속 관계가 아니므로 형변환 불가능
        // Child2 child2 = (Child2) child1;
    }
}

class Parents {
    void print() {
        System.out.println("Hello");
    }
}

class Child1 extends Parents {
    void info() {
        System.out.println("child1");
    }
}

class Child2 extends Parents {
    void info() {
        System.out.println("child2");
    }
}

 

instanceof 연산자

instanceof 연산자는 참조 변수의 형 변환이 가능한 지의 여부를 boolean 타입으로 확인할 수 있는 자바의 문법 요소이다.

즉, 업 캐스팅 또는 다운 캐스팅이 가능한 지를 검사하는 것이다.

 

프로젝트의 규모가 커지고, 클래스가 많아지면 캐스팅 가능 여부를 판단하기 어려워진다. 이러한 문제를 해결해 주는 것이 바로 instanceof 연산자이다.

참조변수 instanceof 타입
public class InstanceOfEx {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal instanceof Object); // true
        System.out.println(animal instanceof Animal); // true
        System.out.println(animal instanceof Dog); // false
        System.out.println(animal instanceof Cat); // false

        Animal cat = new Cat();
        System.out.println(cat instanceof Object); // true
        System.out.println(cat instanceof Animal); // true
        System.out.println(cat instanceof Dog); // false
        System.out.println(cat instanceof Cat); // true

        Animal dog = new Dog();
        System.out.println(dog instanceof Object); // true
        System.out.println(dog instanceof Animal); // true
        System.out.println(dog instanceof Dog); // ture
        System.out.println(dog instanceof Cat); // false
    }
}

class Animal {
}

class Dog extends Animal{
}

class Cat extends Animal{
}

 

다형성의 활용

카페에서 커피를 주문하는 것을 예로

Coffee 클래스에는 가격 정보를 담고 있다.

Coffee 클래스를 상속받는 Amercano 클래스와 CaffeLatte 클래스가 존재한다.

손님은 5만 원을 가지고 있다고 가정한다.

class Coffee {
    int price;

    public Coffee(int price) {
        this.price = price;
    }
}

class Americano extends Coffee {
    public Americano() {
        super(4000); // 상위 메서드 생성자 호출
    }
    // Object 클래스 toString() 메서드 오버라이딩
    public String toString() { 
        return "아메리카노";
    }
}

class CaffeLatte extends Coffee {
    public CaffeLatte() {
        super(5000);
    }
    // Object 클래스 toString() 메서드 오버라이딩
    public String toString() {
        return "카페라떼";
    }
}

class Customer {
    int money = 50000;

    // 커피 구매 메서드(다형성 활용)
    void buyCoffee(Coffee coffee) {
        if (money < coffee.price) {
            System.out.println("잔액이 부족합니다.");
            return;
        }
        money -= coffee.price;
        System.out.println(coffee + "를 구매하였습니다.");
    }

    /* 아메리카노, 카페라떼 구매 메서드를 따로 구현하지 않아도 됨
    void buyCoffee(Americano americano) {
        money -= americano.price;
    }

    void buyCoffee(CaffeLatte caffeLatte) {
        money -= caffeLatte.price;
    } */
}
public class PolymorphismEx {
    public static void main(String[] args) {
        Customer me = new Customer();
        me.buyCoffee(new Americano());
        me.buyCoffee(new CaffeLatte());

        System.out.println("현재 잔액: " + me.money);
    }
}

다음과 같이 다형성을 활용하여 개별 적인 커피의 구매 메서드를 따로 구현하지 않고, 상위 클래스인 Coffee의 자료형을 매개변수로 전달받으면, 하위 클래스 타입의 참조 변수는 매개변수로 전달될 수 있다.

이로써 반복적인 코드를 줄일 수 있다.

반응형