[Java 기초] 다형성

Updated:

이번 포스트에서는 자바에서 중요한 개념중 하나인 다형성에 대해서 알아보도록 하겠다. 👨‍🏫

다형성의 정의

여러 가지 형태를 가질 수 있는 능력

자바에서는 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하여 다형성을 구현하였다.

Car c = new Truck();

두 클래스가 상속관계에 있는 경우, 조상클래스 타입의 참조변수로 자손클래스 인스턴스 참조가 가능.

예제

참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

class Car {
    int seat;
    boolean power;

    void power() { power = !power; }
    void drive() { System.out.println("We drive!"); }
}

class Truck extends Car {
    final int MAX_CARRY;
    void truck() { System.out.println("I'm driving a truck!"); }
}

Truck t = new Truck();

이 경우, Truck 클래스의 모든 멤버와 상속받은 Car 클래스의 모든 멤버를 다 사용할 수 있다.

Car c = new Truck();

반면 멤버의 범위가 더 작은 조상 클래스를 자손 인스턴스의 참조변수로 사용한 경우에는 생성된 자손 인스턴스에서 조상$($Car)으로 부터 상속된 멤버만 사용하는 것이 가능하다.

$($MAX_CARRY, truck() 접근 불가)

단, 조상타입의 인스턴스를 자손타입의 참조변수로 참조하는것은 불가.

참조변수 형변환

참조변수가 다룰 수 있는 멤버의 개수 <= 실제 인스턴스가 갖고 있는 멤버의 개수 이면 형변환 생략 가능.

따라서

  • 자손타입 = 조상타입 : 형변환 생략 불가.
  • 조상타입 = 자손타입 : 형변환 생략 가능.

예제

class Car {
    int seat;
    boolean power;

    void power() { power = !power; }
    void drive() { System.out.println("We drive!"); }
}

class Truck extends Car {
    final int MAX_CARRY = 5;
    void truck() { System.out.println("I'm driving a truck!"); }
}

class Example {
    public static void main(String[] args) {
        Car car = new Car();
        Car car2 = null;
        Truck t = new Truck();
        Truck t2 = null;

        car2 = t;
        t2 = (Truck)car; // 컴파일은 성공. but 실행 시 에러발생.
        
        t2 = (Truck)car2; // 인스턴스가 같다. OK.

        car2.truck(); // 컴파일에러. Car형 참조변수로는 .truck() 접근불가.
        t2.truck();
    }
}

위의 t2 = (Truck)car;의 경우 car 참조변수에 Car타입의 인스턴스가 이미 생성된 상태이기 때문에 조상타입의 인스턴스를 자손타입의 참조변수로 참조하는 것과 같으며 이는 불가능하다. $($정신이 있는 부모가 자식의 품으로 들어가는것은 불가능하다! 라고 생각)

하지만 컴파일과정에서 이러한 에러는 발견되지 않고 실행시에 에러가 발생하기 때문에 시스템에 치명적이다.

따라서 instanceof 연산을 통해 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인해야한다.

어떤 타입에 대한 instanceof 연산 결과가 true이면 검사한 타입으로 형변환이 가능하다.

참조변수와 인스턴스 멤버변수

  • 조상 클래스와 자손 클래스에 멤버변수가 중복으로 정의된 경우.

참조변수가…

조상타입 => 조상 클래스의 멤버변수 사용.

자손타입 => 자손 클래스의 멤버변수를 사용.

👉 참조변수의 멤버변수를 사용한다.


  • 조상 클래스와 자손 클래스에 멤버함수 $($인스턴스 메서드)가 중복 정의된 경우.

참조변수 상관없이 오버라이딩된 메서드 호출.

예제

class Parent {
    int x = 100;

    void method() { System.out.println("Parent Method"); }
}

class Child extends Parent {
    int x = 200;

    void method() { System.out.println("Child Method"); }
}

class Example {
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();

        // 참조변수에 따라 어느 멤버변수를 쓰는지가 결정된다.
        System.out.println("p.x :" + p.x);
        p.method();

        System.out.println("c.x :" + c.x);
        c.method();
    }
}

출력

p.x :100
Child Method
c.x :200
Child Method

매개변수의 다형성

가전제품 클래스와 이 클래스를 상속받는 Tv, Computer 클래스가 있고 제품을 구매하는 구매자 클래스가 있다고 예를들어보겠다.

구매자 클래스에 가전제품을 구매하는 기능의 메서드를 추가해보겠다.

void buy(Tv t) {
    money -= p.price; // 구매자가 가진 돈에서 제품가격을 뺌.
    bonusPoint = bonusPoint + p.bonusPoint;
}

위와같이 매개변수를 선언하게 된다면 가전제품의 수가 늘어날때마다 다른 매개변수를 갖는 같은 기능의 메서드를 계속해서 선언해 주어야 한다.

하지만 매개변수의 다형성을 적용하면 다음과 같이 하나의 메서드로 처리가 가능하다.

void buy(Product p) {
    money -= p.price; // 구매자가 가진 돈에서 제품가격을 뺌.
    bonusPoint = bonusPoint + p.bonusPoint;
}

메서드의 매개변수로 Product클래스의 자손타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다.

예제

class Product {
    int price;
    int bonusPoint;

    Product(int price) {
        this.price = price;
        bonusPoint = (int)(price/10.0);
    }
}

class Tv extends Product {
    Tv() {
        // 조상클래스의 생성자 Product(int price)를 호출
        super(100);
    }

    public String toString() { return "Tv"; }
}

class Computer extends Product {
    Computer() { super(200); }
    public String toString() { return "Computer"; }
}

class Buyer {
    int money = 1000;
    int bonusPoint = 0;

    void buy(Product p) {
        if(money < p.price) {
            System.out.println(" 돈이없어서 물건을 못사요.");
            return;
        }

        money -= p.price;
        bonusPoint += p.bonusPoint;
        System.out.println(p + "을 구입하셨습니다.");
    }
}

class Example {
    public static void main(String[] args) {
        Buyer b = new Buyer();

        b.buy(new Tv());
        b.buy(new Computer());

        System.out.println("현재 남은 돈 : " + b.money + "만원입니다.");
        System.out.println("현재 보너스 점수 : " + b.bonusPoint + "점입니다.");
    }
}

출력

Tv을 구입하셨습니다.
Computer을 구입하셨습니다.
현재 남은 돈 : 700만원입니다.
현재 보너스 점수 : 30점입니다.

여러종류의 객체를 배열로 다루기

조상타입의 참조변수 배열을 사용하면 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다.

Product 클래스가 Tv, Computer 클래스의 조상인 경우 다음과 같이 배열 선언가능.

Product p[] = new Product[2];
p[0] = new Tv();
p[1] = new Computer();

예제

import java.util.*;

class Product {
    int price;
    int bonusPoint;

    Product() {
        price = 0;
        bonusPoint = 0;
    }

    Product(int price) {
        this.price = price;
        bonusPoint = (int)(price / 10.0);
    }
}

class Tv extends Product {
    Tv() { super(100); }
    public String toString() { return "Tv"; }
}

class Computer extends Product {
    Computer() { super(200); }
    public String toString() { return "Computer"; }
}

class Buyer {
    int money = 1000;
    int bonusPoint = 0;
    Vector boughtItems = new Vector();

    void buy(Product p) {
        if(money < p.price) {
            System.out.println("돈이없어서 물건을 못사요.");
            return;
        }
        money -= p.price;
        bonusPoint += p.bonusPoint;
        boughtItems.add(p);
        System.out.println(p + "을 구입했습니다.");
    }

    void refund(Product p) {
        if(boughtItems.remove(p)) {
            money += p.price;
            bonusPoint -= p.bonusPoint;
            System.out.println(p + "을 반품하셨습니다.");
        } else {
            System.out.println("구입하신 제품 중 해당 제품이 없습니다");
        }
    }

    void summary() {
        int sum = 0;
        String itemList = "";

        if(boughtItems.isEmpty()) {
            System.out.println("구입하신 제품이 없습니다.");
            return;
        }

        //구입한 제품의 총 가격과 목록 생성
        for(int i=0; i<boughtItems.size(); i++) {
            Product p = (Product)boughtItems.get(i); // Vector의 i번째 객체를 가져온다.
            sum += p.price;
            itemList += (i == 0) ? "" + p : ", " + p;
        }
        System.out.println("구입하신 물품의 총금액은" + sum + "만원입니다.");
        System.out.println("구입하신 제품은 " + itemList + "입니다.");
    }
}

class Example {
    public static void main(String[] args) {
        Buyer b = new Buyer();
        Tv tv = new Tv();
        Computer com = new Computer();

        b.buy(tv);
        b.buy(com);

        b.summary();
        System.out.println();
        b.refund(tv);
        b.summary();
    }
}

참조
Java의 정석 $($남궁 성)
whiteship 자바 라이브 스터디

Categories:

Updated:

Leave a comment