Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

codingfarm

다형성(polymorphism) 본문

Programming Language/JAVA

다형성(polymorphism)

scarecrow1992 2021. 3. 10. 23:44

1. 다형성이란?

  • 정의 : 여러가지 형태를 가질 수 있는 능력
  • OOP의 4대 특징 중 하나
    • 추상화
    • 캡슐화
    • 상속성
    • 다형성
  • JAVA에서는 조상클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하여 다형성을 구현한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Tv{
    boolean power;
    int channel;
    
    void power() {    power = !power; }
    void channelUp() { ++channel; }
    void channelDown() { --channel; }
}
 
class CaptionTv extends Tv{
    String text;    // 캡션을 보여주기 위한 문자열
    void caption() {/*...*/}
}
cs

Tv 클래스와 CaptionTv 클래스가 위와 같이 정의되어 있을때, 두 클래스간의 관계를 그림으로 그리면 아래와 같다.

다음과 같이 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.

1
Tv t = new CaptionTv();
cs

 

인스턴스를 같은 타입의 참조변수로 참조하는 것과 조상 타입의 참조변수로 참조하는것은 어떤 차이가 있을까?

1
2
Tv t        = new CaptionTv();
CaptionTv c = new CaptionTv();
cs

 

 

모든 클래스의 최고조상인 Object클래스로부터 상속받은 부분은 생략했다.

Tv 타입의 참조변수로는 CaptionTv 인스턴스 중에서 Tv클래스의 멤버들만 사용할 수 있다. 따라서, 생성된 CaptionTv 인스턴스의 멤버 중에서 Tv 클래스에 정의되지 않은 멤버, text와 catption()은 참조변수 t로 사용이 불가능하다.

 

자식 클래스의 참조변수는 부모 클래스의 인스턴스를 참조 할 수 없다.

1
CaptionTv c = new Tv();        // 컴파일 에러 
cs

참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.

 

조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.

 


2. 참조변수의 형변환

  • 다운캐스팅(down-casting) : 조상타입의 참조변수를 자손타입의 참조변수로 변환하는 것
  • 업 캐스팅(up-casting) : 자손 타입의 참조변수를 조상 타입의 참조변수로 변환하는것
    • 업 캐스팅을 위해선 형변환이 필요하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Car {
    String color;
    int door;
    void drive() {
        System.out.println("drive, Brrrr~");
    }
    
    void stop() {
        System.out.println("stop!!!");
    }
}
 
class FireEngine extends Car{    // 소방차
    void water() {
        System.out.println("water!!!");
    }
}
 
class Ambulance extends Car{    // 앰뷸련스
    void siren() {
        System.out.println("siren~~~");
    }
}
cs

위 클래스간의 관계를 그림으로 나타내면 아래와 같다.

 

상속관계가 아닌 클래스의 참조변수와 인스턴스간에는 형변환을 한다 할지라도 참조할 수 없다.

1
2
3
4
5
FireEngine f;
Ambulance a;
 
= (Ambulance) f;        // 컴파일 에러!!
= (FireEngine) a;        // 컴파일 에러!!
cs

 

부모 자식 관계에 있는 클래스의 참조변수 간의 형변환은 아래 처럼 이루어진다.

1
2
3
4
5
6
Cat car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
    
car = fe;
fe2 = (FireEngine)car;
cs

 

아래의 코드는 에러를 발생시킨다.

1
2
Cat car = new Car();
FireEngine fe = (FireEngine) car;    // 에러 
cs

참조변수 car가 참조하고 있는 인스턴스가 Car 타입의 인스턴스 이다. 조상타입의 인스턴스를 자손타입의 참조변수로 참조하는것은 허용되지 않는다.

$\bullet$ 캐스트 연산자를 사용하면 서로 상속 관계에 있는 클래스 타입의 참조변수간의 형변환은 양방향으로 자유롭게 수행될 수 있다.

$\bullet$ 참조변수가 참조하고 있는 인스턴스의 자손타입으로 형변환을 하는 것은 허용되지 않는다.

 

 


3. instanceof 연산자

  • 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용한다.
  • instanceof를 기준으로 각각의 피연산자가 위치한다.
    • 왼쪽 : 참조 변수
    • 오른쪽 : 타입(클래스명)
  • 결과 : boolean(true or false)
    • true : 참조변수가 검사한 타입으로 형변환이 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package project_1;
 
class Car {
    String color;
    int door;
    void drive() {
        System.out.println("drive, Brrrr~");
    }
    
    void stop() {
        System.out.println("stop!!!");
    }
}
 
class FireEngine extends Car{    // 소방차
    void water() {
        System.out.println("water!!!");
    }
}
 
class Ambulance extends Car{    // 앰뷸련스
    void siren() {
        System.out.println("siren~~~");
    }
}
 
public class Hello {
    static void doWork(Car c) {
        if(c instanceof FireEngine) {
            FireEngine fe = (FireEngine) c;
            fe.water();
            //...
        }
        else if(c instanceof Ambulance) {
            Ambulance a = (Ambulance) c;
            a.siren();
            // ...
        }
        // ...
    }
    public static void main(String[] args) {
        Car c = new FireEngine();
        doWork(c);
        
    }    
}
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package project_1;
 
class Car {
    String color;
    int door;
    void drive() {
        System.out.println("drive, Brrrr~");
    }
    
    void stop() {
        System.out.println("stop!!!");
    }
}
 
class FireEngine extends Car{    // 소방차
    void water() {
        System.out.println("water!!!");
    }
}
 
public class Hello {
    public static void main(String[] args) {
        FireEngine fe = new FireEngine();
        
        if(fe instanceof FireEngine) {
            System.out.println("This is a FireEngine instance.");
        }
        
        if(fe instanceof Car) {
            System.out.println("This is a Car instance.");
        }
        
        if(fe instanceof Object) {
            System.out.println("This is a Object instance.");
        }        
    }    
}
cs

 

어떤 타입에 대한 instanceof 연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

 

 


4. 참조변수와 인스턴스의 연결

  • 조상 클래스와 자식 클래스에 같은 이름의 필드와 메서드가 중복으로 정의되었을때, 부모와 자식 클래스중 어느 인스턴스를 참조하는지에 따라 실제 접근하는 필드와 메서드는 달라진다.
    • 메서드 : 항상 실제 인스턴스의 메서드(오버라이딩된 메서드) 가 호출
    • 필드 : 참조변수의 타입에 따라 달라진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package project_1;
 
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");
    }
}
 
 
public class Hello {
    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();
    }    
}
cs

method의 경우 항상 인스턴스의 메서드(Child의 메서드)를 호출하지만 필드의 경우 각 참조변수의 타입을 참조하는것을 볼 수 있다.

부모와 자식 클래스에 이름이 같은 멤버변수가 존재한다면 참조변수의 타입에 따라 접근하는 멤버변수가 달라진다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package project_1;
 
class Parent {
    int x = 100;
    
    void method() {
        System.out.println("Parent Method");
    }
}
 
class Child extends Parent {
    int x = 200;
    
    void method() {
        System.out.println("x = " + x);
        System.out.println("super.x = " + super.x);
        System.out.println("this.x = " + this.x);
    }
}
 
 
public class Hello {
    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();
        System.out.println("c.x = " + c.x);
        c.method();
    }    
}
cs

 


5. 매개변수의 다형성

  • 참조변수의 다형적인 특징은 메서드의 매개변수에도 적용가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Product {
    int price;
    int bonusPoint;
}
 
class Tv extends Product{}
class Computer extends Product {}
class Audio extends Product {}
 
class Buyer {
    int money = 1000;
    int bonusPoint = 0;
}
cs

Buyer클래스에 물건을 구입하는 기능의 메서드를 추가해보자. Product 하위 클래스 제품에 대해 각각 다른 메서드를 정의하면 아래와 같을것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Buyer {
    int money = 1000;
    int bonusPoint = 0;
    
    void buy(Tv t) {
        money = money - t.price;
        bonusPoint = bonusPoint + t.bonusPoint;
    }
    
    void buy(Computer c) {
        money = money - c.price;
        bonusPoint = bonusPoint + c.bonusPoint;
    }
    
    void buy(Audio a) {
        money = money - a.price;
        bonusPoint = bonusPoint + a.bonusPoint;
    }
}
cs

이런식으로 메서드를 작성할 경우 2가지 단점이 존재한다.

  1. 제품의 종류가 늘어날때마다 함수를 새로 작성해야한다.
  2. buy 메서드의 내부 로직에 변화가 생기면 모든 메서드를 다 수정해야한다.

그러나 메서드의 매개변수에 다형성을 적용하면 아래처럼 하나의 메서드로 간단히 처리될 수 있다.

1
2
3
4
5
6
7
8
9
class Buyer {
    int money = 1000;
    int bonusPoint = 0;
    
    void buy(Product p) {
        money = money - p.price;
        bonusPoint = bonusPoint + p.bonusPoint;
    }
}
cs

 

아래 예제코드를 통해 매개변수의 다형성에 대해 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package project_1;
 
class Product {
    int price;
    int bonusPoint;
    
    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 Audio extends Product {
    Audio(){
        super(300);
    }
    
    public String toString() {
        return "Audio";
    }
}
 
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 + "을/를 구입하셨습니다.");
    }
}
 
public class Hello {
    public static void main(String[] args) {
        Buyer b = new Buyer();
        Product tv = new Tv();
        Product a = new Audio();
        Product com = new Computer();
        /*
         Tv tv = new Tv();
        Audio a = new Audio();
        Computer com = new Computer();
        */
        
        b.buy(tv);
        b.buy(com);
        b.buy(a);
        
        System.out.println("현재 남은 돈은 " + b.money + "만원입니다.");
        System.out.println("현재 남은 보너스점수는 " + b.bonusPoint + "점입니다.");
    }
}
cs

 

 


6. 여러종류의 객체를 하나의 배열로 다루기

  • 다형성의 특성을 이용하여 여러 종류의 객체를 하나의 참조변수 배열로 다룰 수 있다.
1
2
3
4
Product p[] = new Product[3];
p[0= new Tv();
p[1= new Computer();
p[2= new Audio();
cs

 

 

 

 

 

'Programming Language > JAVA' 카테고리의 다른 글

인터페이스(interface)  (0) 2021.03.11
추상클래스(abstract class)  (0) 2021.03.11
제어자(modifier)  (0) 2021.03.07
super  (0) 2021.01.07
오버라이딩(overriding)  (0) 2021.01.07
Comments