본문 바로가기
Java

[Java] 가상 메서드 / 상속에서 상위(부모) 클래스와 하위(자식) 클래스의 동일한 이름의 메서드가 있을 때 / 상속 메서드 호출 순서

by bkuk 2022. 10. 10.

가상 메서드란?

 

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

다시 말해 선언한 클래스형이 아닌 생성된 인스턴스를 기준으로 메서드를 호출하는 것입니다. 이렇게 인스턴스의 메서드가 호출되는 기술을 '가상 메서드(virtual method)' 라고 합니다.

클래스 멤버 변수와 메서드로 이루어져 있으며, 클래스를 생성하여 인스턴스가 만들어지면 멤버 변수는 힙 메모리에 위치합니다.

멤버 변수가 사용하는 메모리 메서드가 사용하는 메모리는 다릅니다. 변수는 인스턴스가 생성될 때마다 새로 생성되지만, 메서드는 실행해야 할 명령 집합이기 때문에 인스턴스가 달라도 같은 로직을 수행합니다. 즉, 같은 클래스의 인스턴스를 생성한다고 해서 메서드도 여러 개 생성되지 않습니다.


 

public class VirtualMethod {

	String str = "Hello Java";
	
	public void helloJava() {
		System.out.println( "helloJava() 호출!");
	}

	public static void main(String[] args) {
		
		VirtualMethod vm1 = new VirtualMethod();
		vm1.helloJava();	// Method() 호출
		
		VirtualMethod vm2 = new VirtualMethod();
		vm2.helloJava();	// Method() 호출
		
	}
}

 

 

main() 메서드가 실행되면 지역 변수는 스택 메모리에 위치합니다. 각 참조 변수 vm1,vm2가 가르키는 인스턴스힙 메모리에 생성됩니다. 메서드의 명령 집합은 메서드 영역(코드 영역)에 위치합니다. 우리가 메서드를 호출하면 메서드 영역의 주소를 참조하여 명령이 실행됩니다. 따라서 인스턴스가 달라도 동일한 메서드가 호출됩니다.

 


가상 메서드의 원리

 

일반적으로 프로그램에서 메서드를 호출한다는 것은 그 메서드의 명령 집합이 있는 메모리 위치를 참조하여 명령을 실행하는 것 입니다. 그런데 가상 메서드의 경우에는 '가상 메서드 테이블'이 만들어집니다. 가상 메서드 테이블은 각 메서드 이름과 실제 메모리 주소가 짝을 이루고 있습니다. 어떤 메서드가 호출되면 이 테이블에서 주소 값을 찾아서 해당 메서드의 명령을 수행합니다.

그림에서 보듯이 'payment' 메서드는 두 클래스에서 서로 다른 메서드 주소를 가지고 있습니다. 이렇게 재정의된 메서드는 실제 인스턴스에 해당되는 메서드가 호출됩니다. showMyInfo와 같이 재정의되지 않은 메서드인 경우메서드 주소가 같으며 상위 클래스의 메서드가 호출됩니다.

상위 클래스 (NonMembers 클래스)

public class NonMembers {
	protected String ID;
	protected int shippingFee;
    
	public NonMembers(String name) {
		ID = name;
		shippingFee = 3000;
	}

	public void showMyInfo() {
		System.out.println( "고객님의 아이디는 " + ID + "입니다.");
		System.out.println( "일반고객이므로 배송료는 " + shippingFee + "입니다.");
	}
	
	public void payment (double price) {
		System.out.println( this.ID + "님의 배송료를 포함한 총 결제금액은 " 
		+ (price + shippingFee) + "원 입니다. " );
	}
}

 

하위 클래스 (Members 클래스)

public class Members extends NonMembers{
	
	private double bonusRatio;

	public Members(String name) {
		super(name);
		shippingFee = 1000;
		bonusRatio = 0.01;
	}
	
	public void showBonusRatio() {
		System.out.println( "고객님의 적립비율은 " + bonusRatio + "% 입니다." );
	}
	
	@Override
	public void payment(double price) {
		System.out.println( ID + "님의 배송료를 포함한 총 결제금액은 " 
	    + (price + shippingFee) + "원 입니다. " );
		System.out.println( "적립 금액은 " + (price * bonusRatio) + "원 입니다.");
	}
}

 

실행 클래스 (main 메서드)

package inheritanceEx01;

public class Test01 {

	public static void main(String[] args) {
		
		NonMembers Lee01 = new NonMembers("Lee");
		// payment 메서드 호출
		Lee01.payment( 100_000 );	//Lee님의 배송료를 포함한 총 결제금액은 103000.0원 입니다. 
		
		Members Lee02 = new Members( "Lee02" );
		// 메서드 오버라이딩(재정의)된 payment 메서드 호출
		Lee02.payment( 100_000 );	// Lee02님의 배송료를 포함한 총 결제금액은 101000.0원 입니다.
						// 적립 금액은 1000.0원 입니다.
	}

}

 

 

댓글