본문 바로가기
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()와 같이 재정의 되지 않은 메서드는 메서드 주소가 같으며 상위 클래스의 메서드가 호출됨.

 

 

 

 

📘 비전공자 개발자 취업 성공기 시리즈

개발자가 되고 싶었던 한 비전공자의 1년 4개월 이야기
막막했던 시작부터 좌절, 그리고 합격까지의 여정을 기록했습니다

 

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

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

kmong.com

댓글