JAVA

메서드 오버라이딩, 가상 메서드

seungmin576 2024. 12. 20. 15:14

https://seungmin576.tistory.com/54 코드 계속 사용

상위 클래스 메서드 재정의하기

새로운 등급을 만들면서 VIP 고객에게 제공하는 할인율과 세일 가격을 어떻게 적용할지 구현하지 않았다.

이제 그 문제를 해결한다.

 

상위 클래스 Customer에는 제품 가격을 계산하는 calPrice() 메서드가 이미 정의되어 있다.

public int calcPrice(int price){
	bonusPoint += price * bonusRatio;
    return price;
}

이 메서드는 정가를 그대로 지불한다.

그런데 VIP 고객은 정가에서 10% 할인을 받을 수 있다.

이 경우에 상위 클래스의 calcPrice() 메서드를 그대로 쓸 수 없다.

이렇게 상위 클래스에 정의한 메서드가 하위 클래스에서 구현할 내용과 맞지 않을 경우에 하위 클래스에서 이 메서드를 재정의할 수 있다.

이를 메서드 오버라이딩이라고 한다.

오버라이딩을 하려면 반환형, 메서드 이름, 매개변수 개수, 매개변수 자료형이 반드시 같아야 한다.

그렇지 않으면 자바 컴파일러는 재정의한 메서드를 기존 메서드와 다른 메서드로 인식한다.

 

VIP 고객 클래스의 제품 가격 계산 메서드 재정의하기

VIPCustomer 클래스에서 calcPrice() 메서드를 재정의한다.

package inheritance;

public class VIPCustomer extends Customer{
    private int agentID;
    double saleRatio;

//    public  VIPCustomer(){
//        customerGrade = "VIP";
//        bonusRatio = 0.05;
//        saleRatio = 0.1;
//        System.out.println("VIPCustomer() 생성자 호출");
//    }

    @Override
    public int calcPrice(int price){
        bonusPoint += price * bonusRatio;
        return price - (int)(price * saleRatio);
    }

    public VIPCustomer(int customerID, String customerName, int agentID){
        super(customerID, customerName);    //상위 클래스 생성자 호출
        customerGrade = "VIP";
        bonusRatio = 0.05;
        saleRatio = 0.1;
        this.agentID = agentID;
        System.out.println("VIPCustomer(int, String, int) 생성자 호출");
    }
    
    public int getAgentID(){
        return agentID;
    }
}

하위 클래스 VIPCustomer에서 calcPrice() 메서드를 재정의했다.

상위 클래스 calcPrice()메서드와 매개변수의 자료형 및 개수가 같고, 반환형도 int형으로 같다.

할인율을 계산하여 정가에서 뺀 후 세일 가격을 반환하는 문장이 추가됐다.

@Override 애노테이션은 '이 메서드는 재정의된 메서드이다' 라고 컴파일러에 명확히 알려주는 역할을 한다.

 

  • 두 고객을 생성해서 지불하는 가격을 출력해 보는 테스트 프로그램
package inheritance;

public class OverridingTest1 {
    public static void main(String[] args){
        Customer customerUna = new Customer(10010, "웅나");
        customerUna.bonusPoint = 1000;

        VIPCustomer customerNana = new VIPCustomer(10020, "나나", 12345);
        customerNana.bonusPoint = 10000;

        int price = 10000;

        System.out.println(customerUna.getCustomerName() + " 님이 지불해야 하는 금액은 " + customerUna.calcPrice(price) + "원입니다.");
        System.out.println(customerNana.getCustomerName() + " 님이 지불해야 하는 금액은 " + customerNana.calcPrice(price) + "원입니다.");
    }
}

웅나는 기본고객이니 정가 그대로 지불, 나나는 VIP 고객이니 10% 할인이 된 모습이다.


묵시적 클래스 형 변환과 메서드 재정의

Customer vc = new VIPCustomer(10030, "뭐야", 2000);
vc.calcPrice(10000);

묵시적 형 변환에 의해 VIPCustomer가 Customer 형으로 형 변환되었다.

그리고 나서 calcPrice() 메서드가 호출되었다.

clacPrice()는 하위 클래스에서 재정의된 메서드이며 Customer 클래스와 VIPCustomer 클래스에 모두 존재한다.

그렇다면 vc.calcPrice(10000);은 어떤 클래스의 메서드를 호출할까?

 

  • 고객이 지불해야 하는 금액은 얼마일지 테스트
package inheritance;

public class OverridingTest2 {
    public static void main(String[] args){
        Customer vc = new VIPCustomer(10030, "무야", 2000);
        vc.bonusPoint = 1000;

        System.out.println(vc.getCustomerName() + " 님이 지불해야 하는 금액은 " + vc.calcPrice(10000) + "원입니다.");
    }
}

멤버 변수와 메서드는 선언한 클래스형에 따라 호출된다.

그러면 vc.calcPrice(10000)은 선언한 클래스형인 Customer 클래스의 calcPrcie() 메서드르 호출해야한다.

그런데 출력 결과를 보니 구천원이다.

VIPCustomer 클래스의 calcPrice() 메서드가 호출되었음을 알 수 있다.

 

상속에서 상위 클래스와 하위 클래스에 같은 이름의 메서드가 존재할 때 호출되는 메서드는 인스턴스에 따라 결정된다.

다시 말해 선언한 클래스형이 아닌 생성된 인스턴스의 메서드를 호출하는 것이다.

이렇게 인스턴스의 메서드가 호출되는 기술을 '가상 메서드'라고 한다.

가상 메서드가 실행되는 원리를 이해하면 왜 vc.calcPrice(10000)이 Customer 클래스의 메서드가 아닌 생성된 인스턴스, 즉 VIPCustomer 메서드를 호출하는지 이해할 수 있다.


가상 메서드

자바의 클래스는 멤버 변수와 메서드로 이루어져 있다.

클래스를 생성하여 인스턴스가 만들어지면 멤버 변수는 힙 메모리에 위치한다.

그렇다면 메서드는 어디에 위치할까? 변수가 사용하는 메모리와 메서드가 사용하는 메모리는 다르다.

변수는 인스턴스가 생성될때마다 새로 생성되지만, 메서드는 실행해야 할 명령 집합이기 때문에 인스턴스가 달라도 같은 로직을 수행한다.

즉 같은 객체의 인스턴스를 여러개 생성한다고 해서 메서드도 여러 개 생성되지 않는다.

 

  • 간단 예시
package virtualfunction;

public class TestA {
    int num;

    void aaa(){
        System.out.println("aaa() 출력");
    }

    public static void main(String[] args){
        TestA a1 = new TestA();
        a1.aaa();
        TestA a2 = new TestA();
        a2.aaa();
    }
}

 

위 코드가 실행되는 메모리의 상태

스택 메모리가 선입후출이라 그림을 보면 a1과 a2의 위치가 바뀌거나 args가 맨위로 올라가야 선입후출이 제대로 된 그림 같은데 책에선 이렇게 그려져있다. 내 생각엔 a1 a2 위치가 잘못되었다.

main() 함수가 실행되면 지역 변수는 스택 메모리에 위치한다.

그리고 각 참조 변수 a1과 a2가 가리키는 인스턴스는 힙 메모리에 생성된다.

메서드의 명령 집합은 메서드 영역(코드 영역)에 위치한다.

우리가 메서드를 호출하면 메서드 영역의 주소를 참조하여 명령이 실행된다.

따라서 인스턴스가 달라도 동일한 메서드가 호출된다.

 

가성 메서드의 원리

일반적으로 프로그램에서 메서드를 호출한다는 것은 그 메서드의 명령 집합이 있는 메모리 위치를 참조하여 명령을 실행하는 것이다.

그런데 가상 메서드의 경우에는 '가상 메서드 테이블'이 만들어진다.

가상 메서드 테이블은 각 메서드 이름과 실제 메모리 주소가 짝을 이루고 있다.

어떤 메서드가 호출되면 이 테이블에서 주소 값을 찾아서 해당 메서드의 명령을 수행한다.

 

  • Customer 클래스와 VIPCustomer 클래스의 가상 메서드 테이블

 

그림에서 보듯이 calcPrice() 메서드는 두 클래스에서 서로 다른 메서드 주소를 가지고 있다.

이렇게 재정의된 메서드는 실제 인스턴스에 해당하는 메서드가 호출된다.

showCustomerInfo()와 같이 재정의되지 않은 메서드인 경우는 메서드 주소가 같으며 상위 클래스의 메서드가 호출된다.

 

  • 가격이 만원인 상품이 있을 때, Customer 클래스로 인스턴스를 생성한 경우와 VIPCustomer 클래스로 인스턴스를 생성하여 Customer 클래스형으로 형 변환한 경우 각각 얼마를 지불해야 하는지
package inheritance;

public class OverridingTest3 {
    public static void main(String[] args){
        int price = 10000;

        Customer customerLee = new Customer(10010, "이순신");
        System.out.println(customerLee.getCustomerName() + " 님이 지불해야 하는 금액은 " + customerLee.calcPrice(price) + "원입니다.");

        VIPCustomer customerKim = new VIPCustomer(10020, "김유신", 12345);
        System.out.println(customerKim.getCustomerName() + " 님이 지불해야 하는 금액은 " + customerKim.calcPrice(price) + "원입니다.");


        Customer vc = new VIPCustomer(10030, "읭위욍", 2000);
        System.out.println(vc.getCustomerName() + " 님이 지불해야 하는 금액은 " + vc.calcPrice(price) + "원입니다.");
    }
}

7행에서 Customer형으로 선언하고 Customer 인스턴스를 생성하면 Customer의 메서드가 호출.

따라서 customerLee가 지불해야 할 가격은 할인이 안 된 만원이다.

그리고 10행에서 VIPCustomer로 생성한 customerKim은 당연히 할인된 가격은 구천원을 지불한다.

마지막으로 13행에서 VIPCustomer로 생성하고 Customer형으로 변환한 vc는 원래 Customer형 메서드가 호출되는 것이 맞지만, 가상 메서드 방식에 의해 VIPCustomer 인스턴스의 메서드가 호출되어 할인 가격 구천원이 출력된다.

정리해보면 상위 클래스(Customer)에서 선언한 calcPrice() 메서드가 있고 이를 하위 클래스(VIPCustomer)에서 재정의한 상태에서 하위 클래스 인스턴스(vc)가 상위 클래스로 형 변환이 되었다.

이때 vc.calcPrice()가 호출되면, vc 변수를 선언할 때 사용한 자료형(Customer)의 메서드가 호출되는 것이 아니라 생성된 인스턴스(VIPCustomer)의 메서드가 호출된다

이를 가상 메서드라고한다.

자바의 모든 메서드는 가상 메서드이다.

 

 

'JAVA' 카테고리의 다른 글

다형성 활용하기  (0) 2024.12.23
다형성  (0) 2024.12.20
상속에서 클래스 생성과 형 변환, super()  (0) 2024.12.19
상속  (1) 2024.12.19
ArrayList 사용한 배열 응용 프로그램  (2) 2024.12.19