다형성(polymorphism)이란?

- 하나의 코드가 여러 자료형으로 수현되어 실행되는 것

- 같은 코드에서 여러 다른 실행결과가 나옴

- 정보은닉, 상속과 더불어 객체지향 프로그래밍의 가장 큰 특징 중 하나

- 다형성을 잘 활용하면 유연하고 확장성있고 유지보수가 편리한 프로그램을 만들 수 있음

ex)

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
class Animal {
    public void move() {
        System.out.println("동물이 움직입니다.");
    }
}
 
class Human extends Animal {
    @Override
    public void move() {
        System.out.println("사람이 두 발로 걷습니다.");
    }
    
    public void readBook() {
        System.out.println("사람이 책을 읽습니다.");
    }
}
 
class Tiger extends Animal {
    @Override
    public void move() {
        System.out.println("호랑이가 네 발로 뜁니다.");
    }
    
    public void hunting() {
        System.out.println("호랑이가 사냥을 합니다.");
    }
}
 
class Eagle extends Animal {
    @Override
    public void move() {
        System.out.println("독수리가 하늘을 날아다닙니다.");
    }
    
    public void flying() {
        System.out.println("독수리가 양 날개를 쭉 펴고 날아다닙니다.");
    }
}
 
public class AnimalTest {
    public static void main(String[] args) {
        Animal hAnimal = new Human();
        Animal tAnimal = new Tiger();
        Animal eAnimal = new Eagle();
 
        AnimalTest test = new AnimalTest();
        test.moveAnimal(hAnimal);
        test.moveAnimal(tAnimal);
        test.moveAnimal(eAnimal);
 
        ArrayList<Animal> animalList = new ArrayList<Animal>();
        animalList.add(hAnimal);
        animalList.add(tAnimal);
        animalList.add(eAnimal);
        
        for(Animal animal : animalList) {
            animal.move();
        }
 
    }
 
    public void moveAnimal(Animal animal) {
        animal.move();
    }
}
cs

 

결과 :

사람이 두 발로 걷습니다.

호랑이가 네 발로 뜁니다.

독수리가 하늘을 날아다닙니다.

사람이 두 발로 걷습니다.

호랑이가 네 발로 뜁니다.

독수리가 하늘을 날아다닙니다.

 

 

다형성을 사용하는 이유

- 상속과 메서드 재정의를 활용하여 확장성있는 프로그램을 만들 수 있음

- 코드의 유지보수가 편리해짐

- 상위 클래스에서는 공통적인 부분을 제공하고 하위 클래스에서는 각 클래스에 맞는 기능 구현

- 여러 클래스를 하나의 타입(상위 클래스)으로 핸들링할 수 있음

하위 클래스에서 매서드 재정의 하기

- 오버라이딩(overriding) : 상위 클래스에 정의된 매서드의 구현 내용이 하위 클래스에서 구현할 내용과 맞지 않는 경우 하위 클래스에서 동일한 이름의 매서드를 재정의할 수 있음

 

ex) VIPCustomer 클래스의 calcPrice()에 할인율을 적용하여 재정의

 

Customer.java

1
2
3
4
public int calcPrice(int price) {
    bonusPoint += price * bonusRatio;
    return price;
}
cs

VIPCustomer.java

1
2
3
4
5
6
@Override
public int calcPrice(int price) {
    bonusPoint += price * bonusRatio;
    price -= (int)(price * salesRatio);
    return price;
}
cs

 

 

@Override 어노테이션

- 어노테이션은 원래 주석이라는 의미

- 컴파일러에게 특별한 정보를 제공해주는 역할

- @Override는 재정의된 매서드라는 의미로 선언부가 기존의 매서드와 다른 경우 에러남

 

형 변환과 오버라이딩 매서드 호출

Customer vc = new VIPCustomer();

vc변수의 타입은 Customer지만 인스턴스의 타입은 VIPCustomer이다.

Java에서는 항상 ❗인스턴스의 매서드가 호출된다. (가상 매서드의 원리)

자바의 모든 매서드는 가상 매서드(virtual method) 이다.

 

 

메서드는 어떻게 호출되고 실행되는가?

- 메서드(함수)의 이름은 주소값을 나타냄

- 메서드는 명령어의 set이고 프로그램이 로드되면 메서드 영역(코드 영역)에 명령어 set이 위치

- 해당 메서드가 호출되면 명령어 set이 있는 주소를 찾아 명령어가 실행됨

- 이 때 메서드에서 사용하는 변수들은 스택 메모리에 위치하게 됨

- 따라서 다른 인스턴스라도 같은 메서드의 코드는 같으므로 같은 메서드가 호출됨

- 인스턴스가 생성되면 변수는 힙 메모리에 따로 생성되지만, 메서드 명령어 set은 처음 한 번만 로드됨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestMethod {
    int num;
    void aaa() {
        System.out.println("aaa() 호출");
    }
 
    public static void main(String[] args) {
        TestMethod a1 = new TestMethod();
        a1.aaa();
 
        TestMethod a2 = new TestMethod();
        a2.aaa();
    }
}
cs

 

가상 메서드의 원리

- 가상 메서드 테이블(virtual method table)에서 해당 메서드에 대한 address를 가지고 있음

- 재정의된 경우는 재정의된 메서드의 주소를 가리킴

 

출처 : 패스트캠퍼스

 

<전제조건>

1
2
3
public class VIPCustomer extends Customer() {
 
}
cs

 

하위 클래스가 생성되는 과정

- 하위 클래스를 생성하면 상위 클래스가 먼저 생성됨

- new VIPCustomer()를 호출하면 Customer()가 먼저 호출됨

 

 

상위 클래스를 호출하는 코드가 없는데, 어떻게 하위 클래스의 생성자는 상위 클래스를 호출할까?

=> 컴파일러가 자동으로 하위 클래스의 생성자에 super(); 를 넣어준다!!

=> super(); 는 상위 클래스의 디폴트 생성자를 호출한다.

=> super.상위클래스에구현된함수 : 이렇게 사용할 수 있다.

===> 즉, super는 상위클래스에 접근할 수 있게 한다. (물론 super 없이 그냥 써도 가능)

 

** 상위 클래스에 디폴트 생성자가 없는 경우(매개변수가 있는 다른 생성자를 선언한 경우)에는 하위 클래스의 생성자도 형태를 맞춰주고 super(매개변수)를 명시적으로 써주어야 한다.

 

 

❗❗super 키워드 정리

- 하위 클래스에서 가지는 상위 클래스에 대한 참조값

- super()는 상위 클래스의 기본 생성자를 호출함

- 하위 클래스에서 명시적으로 상위 클래스의 생성자를 호출하지 않으면 super()가 호출됨

  (이때 반드시 상위 클래스의 기본 생성자가 존재해야 함)

- 상위 클래스의 기본 생성자가 없는경우(다른 생성자가 있는 경우) 하위 클래스 생성자에서는 super를 이용하여 명시적으로 상위 클래스의 생성자를 호출해야 함

- super는 생성된 상위 클래스 인스턴스의 참조값을 가지므로 super를 이용하여 상위 클래스의 매서드나 멤버변수에 접근할 수 있음

 

 

상속에서 인스턴스 메모리의 상태

- private 변수도 메모리에 생성되며, 접근을 못하는것 뿐이다. -> 지난번 예제에서 접근할 수 있게 protected로 변경함.

힙 메모리

 

형 변환(업캐스팅)

- 상위 클래스로 변수를 선언하고 하위 클래스의 생성자로 인스턴스를 생성

- 하위 클래스는 상위 클래스를 내포하고 있기 때문에 가능(위 메모리 상태 참고)

1
Customer customerLee = new VIPCustomer();
cs

- 상위 클래스 타입의 변수에 하위 클래스 변수가 대입

1
2
3
4
5
VIPCustomer vCustomer = new VIPCustomer();
addCustomer(vCustomer);
int addCustomer(Customer customer) {
 
}
cs

- 하위 클래스는 상위 클래스의 타입을 내포하고 있으므로 상위 클래스로의 묵시적 형 변환이 가능함

- 상속관계에서 모든 하위 클래스는 상위 클래스로 형 변환(업캐스팅)이 됨 (그 역은 성립하지 않음❌)

 

형 변환과 메모리

- Customer vc = new VIPCustomer(); 에서 vc가 가리키는 것은?

- VIPCustomer()생성자에 의해 VIPCustomer 클래스의 모든 멤버변수에 대한 메모리는 생성됐지만, 변수의 타입이 Customer 이므로 실제 접근 가능한 변수나 매서드는 Customer의 변수와 매서드이다.

 

클래스의 계층구조가 여러 단계일 수 있다!

 

출처 : 패스트캠퍼스

+ Recent posts