본문 바로가기
Programming Language/Java

[자바] 상속에 대하여 - (2) 오버라이딩, 가상 메서드

by ggyongi 2021. 10. 23.
반응형

상속에서 중요한 것 중에 클래스 간의 형 변환이 있다.

 

이전 예시인 Customer와 VIPCustomer 관계를 생각해볼 때

하위 클래스인 VIPCustomer는 상위 클래스인 Customer보다 더 많은 기능을 갖고 있음

상속받은 클래스는 상위 클래스 기능을 모두 사용 + 본인의 기능 사용이기 때문이다.

따라서 VIPCustomer는 VIPCustomer형인 동시에 Customer형이라고 할 수 있다.

그래서 다음과 같이 형변환이 가능해짐

Customer customerKim = new VIPCustomer(2020, "김유신", 111);

이때 이 customerKim은 정체가 뭘까?

VIPCustmer 생성자가 호출되므로 VIPCustomer의 변수, 메서드가 메모리에 생성됨.

그런데 클래서 자료형이 Customer로 제한되어 있음.

이 경우에 customerKim 참조 변수가 가리킬 수 변수와 메서드는 Customer 클래스의 멤버로 제한이 됨

 

이런 클래스 형 변환이 왜 필요하지? -> 다음 내용의 '오버라이딩'과 '다형성'에서 그 이유를 찾을 수 있음..!

 

 

- 메서드 오버라이딩

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

 

- 오버라이딩의 조건

반환형, 메서드 이름, 매개변수 개수, 매개변수 자료형이 반드시 같아야 함.

그러지 않으면 자바 컴파일러가 이 메서드를 기존 메서드와 다른 메서드로 인식 해버림

 

- 예시

VIPCustomer 클래스에서 calcPrice() 메서드를 재정의 해보자.

 

테스트

package first_project;

public class Customer {
	protected int customerId;
	protected String customerName;
	protected String customerGrade;
	int bonusPoint;
	double bonusRatio;

	public int getCustomerId() {
		return customerId;
	}

	public void setCustomerId(int customerId) {
		this.customerId = customerId;
	}

	public String getCustomerName() {
		return customerName;
	}

	public void setCustomerName(String customerName) {
		this.customerName = customerName;
	}

	public String getCustomerGrade() {
		return customerGrade;
	}

	public void setCustomerGrade(String customerGrade) {
		this.customerGrade = customerGrade;
	}

	public Customer(int customerId, String customerName) {
		this.customerId = customerId;
		this.customerName = customerName;
		customerGrade = "Silver";
		bonusRatio = 0.01;
		//System.out.println("Customer(int, String) 생성자 호출됨");
	}
	
	public int calcPrice(int price) {
		bonusPoint += price* bonusRatio;
		return price;
	}
	
	public String showCustomerInfo() {
		return customerName+ "님의 등급은 "+customerGrade+"이며, 보너스 포인트는 "+bonusPoint+"입니다.";
	}
	
}

 

package first_project;

public class VIPCustomer extends Customer {

	private int agentId;
	double 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) 생성자 호출됨");
	}
	

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


	public int getAgentId() {
		return agentId;
	}
	
	
}

 

package first_project;


public class CustomerTest {

	public static void main(String[] args) {

		Customer customerLee = new Customer(1010, "이순신");
		customerLee.bonusPoint = 1000;
		
		VIPCustomer customerKim = new VIPCustomer(2020, "김유신", 111);
		customerKim.bonusPoint = 10000;
		
		int price = 10000;
		System.out.println(customerLee.getCustomerName()+"님의 지불 금액은 "+ customerLee.calcPrice(price)+"원입니다. ");
		System.out.println(customerKim.getCustomerName()+"님의 지불 금액은 "+ customerKim.calcPrice(price)+"원입니다. ");
		
	}

}

결과: 오버라이딩 메서드에 의해 서로 다른 가격이 산출되었다. 

이순신님의 지불 금액은 10000원입니다. 
김유신님의 지불 금액은 9000원입니다.

 

 

- 다음과 같은 경우에는 어떻게 될까?

묵시적 형변환에 의해 VIPCustomer형에서 Customer형으로 변환된 경우. 한번 예상해보자.

Customer vc = new VIPCustomer(3030, "메롱이", 2000);
vc.bonusPoint = 1000;
System.out.println(vc.getCustomerName()+"님의 지불 금액은 "+ vc.calcPrice(price)+"원입니다. ");

결과

메롱이님의 지불 금액은 9000원입니다.

Customer형으로 선언했으니 당연히 Customer 클래스 내에 있는 calcPrice()를 호출할 것이라 예상했지만,

예상과는 다르게 VIPCustomer 클래스의 calcPrice(), 즉 재정의된 메서드가 호출되었다.

 

이유는??

상속에서 상위 클래스와 하위 클래스에 같은 이름의 메서드가 존재하면, 호출되는 메서드는 인스턴스에 의해 결정됨.

즉 선언한 클래스가 아니고 생성된 인스턴스의 메서드를 호출함.

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

 

 

- 가상 메서드

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

반면 메서드는 사용하는 메모리가 다름. 

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

메서드 명령 집합은 메서드(코드) 영역에 위치하게 됨

 

- 가상 메서드의 원리

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

근데 가상 메서드는 '가상 메서드 테이블'을 만듦. 이 테이블은 각 메서드 이름과 실제 메모리 주소가 짝을 이루고 잇어서 어떤 메서드가 호출되면 이 테이블을 통해 주소값을 찾고 해당 메서드의 명령을 수행함.

 

- 이를 토대로 Customer 클래스와 VIPCustomer 클래스를 살펴보자.

calcPrice() 메서드는 두 클래스에서 서로 다른 메서드 주소를 갖고 있음. 이렇게 재정의된 메서드는 실제 인스턴스에 해당하는 메서드가 호출됨.

반면 showCustomerInfo()와 같이 재정의 되지 않은 메서드는 메서드 주소가 같으며 상위 클래스의 메서드가 호출됨.

 

 

 

 

 

비전공자 네카라 신입 취업 노하우

시행착오 끝에 얻어낸 취업 노하우가 모두 담긴 전자책!

kmong.com

댓글