Java의 정석 - 객체의 다형성

원태연·2022년 5월 30일
0

Java의 정석

목록 보기
8/19
post-thumbnail

다형성

다형성은 말 그대로 여러가지 형태를 가진 다는 뜻.

객체지향에서 중요한 개념으로, 결론부터 말하면 사용할 수 있는 멤버변수의 개수를 바꾼다 라고 할 수 있다.

class Person{}
Person me = new Person();

보통 인스턴스화 하면, 위와 같이 참조변수의 타입과 인스턴스의 타입이 동일해야하지만, 필요에 의하면 일치하지 않아도 된다. 단, 그 인스턴스 타입이 참조변수의 자손인 경우만 가능하다. 다형성을 통해 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있다.

class Person{
  int age = 10;
}
class Child extends Person{
    int name = 0;
}
public class playGround {
    public static void play() {
        Person me = new Korean();  //--1
        Korean me2 = new Korean();  //--2
//      System.out.println(me.name);
//      참조 타입이 Person이라 Korean의 멤버변수에 접근 불가
        System.out.println(me2.name);
//      Korean me = new Person(); 자손의 경우만 가능
    }
}

--1을 보면, me라는 참조변수의 타입을 Person 이라 선언하고, Korean타입의 객체를 참조하도록 하였다.

인스턴스는 Korean()이지만 참조변수의 타입이 Person()이라 Korean()의 name이라는 멤버변수를 사용할 수 없다.

참조변수의 형변환

int a = 10.3;
int b = (float)a; // 10

위 코드 처럼 형변환(casting)을 참조변수간에서도 가능하다. 물론, 두 클래스는 반드시 상속관계이어야 한다.

Korean k = new Korean();
Person me = (Person)k;    //--1

--1을 보면, Person타입의 참조변수 me에 k를 대입하였다. 앞에 (Person)을 붙혀, Korean 타입의 k 가 아닌, Person타입으로 형변환이 된 후, 대입이 되었다는 것을 알 수 있다.

Person me = (Person)k;처럼 참조변수의 타입이 자손타입 -> 조상타입으로 변경하는 경우에는 (Person)을 생략해도 되지만, 반대의 경우는 불가하다.

class Person{ int age = 10;}
class Korean extends Person{ String name = "T";}

Person me = new Person();
Korean me2 = (Korean)me;
//System.out.println(me2.name); //ERROR

부모가 자손의 참조변수 타입으로 형변환 하는 경우에 대해서 주의할 필요가 있다.

위 코드를 보면 me2.name이 존재할 것 같지만, 존재하지 않는다. 왜냐하면, me는 Person타입의 인스턴스이기 때문에, name이라는 인스턴스 변수는 생성되지 않는다. 이때, Korean me2 = (Korean)me; 라는 것은, 존재하지 않는 .name에 접근할 수 있는 Korean 참조변수 타입으로 형변환 되었기 때문이다.

그래서 컴파일이 되기 전에는, me2의 참조변수 타입은 알 수 있지만, 실제로 어떤 멤버들이 들어있는지는 모르기 때문에 실행 전에는 오류가 나는지 알 수 없다.

instanceof

이런 오류를 방지하고자 instanceof연산자가 존재 한다.

두 참조변수간의 형변환이 가능하면 true를 반환하는 연산자이다.

다형성의 이유

package playground;

class Product{
    int price;
    int bonusPoint;

    public Product(int price) {
        this.price = price;
        this.bonusPoint = (int) (price / 10.0);
    }
}
class Tv extends Product{
    Tv(){super(400);}
    public String toString(){return "TV";}
}
class Computer extends Product{
    Computer(){super(500);}
    public String toString(){return "Computer";}
}
class Audio extends Product{
    Audio(){super(600);}
    public String toString(){return "Audio";}
}
class Buyer{
    int money;
    int bonusPoint;

    Buyer(int money, int bonusPoint) {
        this.money = money;
        this.bonusPoint = bonusPoint;
    }

    void buy(Product p){
        if (money < p.price){
            System.out.println("잔액 부족");
            return;
        }
        this.money -= p.price;
        bonusPoint += p.bonusPoint;
        System.out.println("구매 완료");
    }
}
public class playGround {
    public static void play() {
        Buyer me = new Buyer(1000,0);

        me.buy(new Tv());                  
        me.buy(new Computer());
        me.buy(new Audio());
    }
}

위 코드는 다형성을 통해 좀 더 객체지향적이고 하드코딩을 많이 줄였다.

me.buy(new Tv()); 를 보면, void buy(Product p) 참조변수의 형변환을 통해 자손인 Tv(), Computer(), Audio()와 같은 자손 클래스들에 대해 한 가지 메소드를 재사용하였다. 만일, 가능하지 않다면, 각각의 타입마다 메소드를 하나씩 같은 내용으로 정의하였을 것이다.

다형성을 통해 빡빡하게만 여겨지는 타입간의 조작이 유연해졌다.

TV, Computer, Audio와 같이 비슷하지만 다른 타입인 녀석들을 공통적인 부분들에 대해서는 일괄적으로 다룰 수 있게 되었다.

이를 응용하여 배열로도 이어지는데, Product[]라는 배열에 (p)Tv, (p)Computer, (p)Audio처럼 처리할 수 있다.

profile
앞으로 넘어지기

0개의 댓글