본문 바로가기

Language/Java

[Java] 메서드 오버라이딩

이전에 상속을 공부하면서 만든 상위 클래스 Customer에는 제품 가격을 계산하는 calcPrice() 메서드가 이미 정의되어 있다.

이 메서드는 정가를 그대로 지불하지만, VIP 고객은 정가에 10%를 할인받을 수 있다.

이러한 경우 상위 클래스에서 정의한 calcPrice() 메서드를 하위 클래스에서 구현할 내용과 맞지 않을 경우 하위 클래스에서 이 메서드를 재정의할 수 있는데 이를 메서드 오버 라이딩(method overriding)이라 한다.

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

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

 

[실습] calcPrice() 메서드 재정의하기
package inheritance;

public class VIPCustomer extends Customer {
	private int agentID;  // VIP 고객 상담원 아이디
	double saleRatio;     // 할인율
	
...
	
// 재정의한 메서드
	@Override  // 재정의된 메서드임을 컴파일러에 알려주는 역할을 하는 애노테이션
	public int calcPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price - (int)(price * saleRatio);  // 할인된 가격을 계산하여 반환
	}
}

 

애노테이션(Annotation)이란?

애노테이션은 영어로 주석이라는 의미이다. @ 기호와 함께 사용하며 '@애노테이션 이름'으로 표현한다.
자바에서 제공하는 애노테이션은 컴파일러에게 특정한 정보를 제공해 주는 역할을 한다.
예를 들어 @Override는 이 메서드가 재정의된 메서드임을 컴파일러에게 알려준다.
만약 메서드의 선언부가 다르다면 컴파일 오류가 발생하여 프로그래머의 실수를 막아준다.
이렇게 미리 정의되어 있는 애노테이션을 표준 애노테이션이라 한다.

애노테이션 설명
@Override 재정의된 메서드라는 정보 제공
@FuctionalInterface 함수형 인터페이스라는 정보 제공
@Deprecated 이후 버전에서 사용되지 않을 수 있는 변수, 메서드에 사용됨
@SuppressWarnings 특정 경고가 나타나지 않도록 함

 

[실습] calcPrice() 테스트하기
package inheritance;

public class OverridingTest1 {
	public static void main(String[] args) {
		Customer customerLee = new Customer(10010, "이순신");
		customerLee.bonusPoint = 1000;
		
		VIPCustomer customerKim = new VIPCustomer(10020, "김유신", 12345);
		customerKim.bonusPoint = 10000;
		
		int price = 10000;
		System.out.println(customerLee.getCustomerName() + 
			" 님이 지불해야 하는 금액은 " + customerLee.calcPrice(price) + "원입니다.");
		System.out.println(customerKim.getCustomerName() + 
			" 님이 지불해야 하는 금액은 " + customerKim.calcPrice(price) + "원입니다.");
	}
}

 

그렇다면 다음과 같은 경우는 어떻게 실행이 될까?

Customer vc = new VIPCustomer(10030, "나몰라", 2000);
vc.calcPrice(10000);

묵시적 형 변환에 의해 VIPCustomer가 Customer형으로 변환된 상황에서 호출된 메서드 calcPrice()는 어떤 클래스에서 호출이 되는지 확인해보자.

 

[실습] 클래스 형 변환과 재정의 메서드 호출하기
package inheritance;

public class OverridingTest2 {
	public static void main(String[] args) {
		Customer vc = new VIPCustomer(10030, "나몰라", 2000);  //VIP 고객 생성
		vc.bonusPoint = 1000;
		
		System.out.println(vc.getCustomerName() + " 님이 지불해야 하는 금액은 "
				+ vc.calcPrice(10000) + "원입니다.");
	}
}

 

멤버 변수와 메서드는 선언한 클래스형에 따라 호출되는데, 여기서는 선언한 클래스형인 Customer 클래스의 calcPrice() 메서드가 호출된 것이 아니라 VIPCustomer 클래스의 calcPrice() 메서드, 즉 재정의한 메서드가 호출되었다.

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

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

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

 

 

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

멤버 변수는 힙 메모리에 위치하여 인스턴스가 생성될 때마다 새로 생성된다.

메서드는 실행해야 할 명령 집합이기 때문에 인스턴스가 달라도 같은 로직을 수행한다.

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

[실습] 메서드 호출하기
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();
	}
}

 

위 코드가 실행되는 메모리의 상태를 그림으로 그리면 아래와 같다.

 

main() 함수가 실행되면 지역 변수는 스택 메모리에 위치하고, 각 참조 변수 a1, a2가 가리키는 인스턴스는 힙 메모리에 생성된다.

메서드의 명령 집합은 메서드 영역(코드 영역)에 위치하여, 메서드 호출 시 메서드 영역의 주소를 참조하여 명령이 실행된다.

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

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

가상 메서드의 경우 '가상 메서드 테이블'이 만들어지는데, 이는 각 메서드 이름과 실제 메모리 주소가 짝을 이룬다.

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

 

[실습] 클래스형에 기반하여 지불 금액 계산하기
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) + "원입니다.");
		
	}
}

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

상위 클래스(Customer)에서 선언한 calcPrice() 메서드가 있고, 이를 하위 클래스(VIPCustomer)에서 재정의한 상태에서 하위 클래스 인스턴스(vc)가 상위 클래스로 형 변환이 되었다. 이때 vc.calcPrice()가 호출되면, vc 변수를 선언할 때 사용한 자료형(Customer)의 메서드가 호출되는 것이 아니라 생성된 인스턴스(VIPCustomer)의 메서드가 호출된다. 이를 가상 메서드라 한다. 자바의 모든 메서드는 가상 메서드이다.

'Language > Java' 카테고리의 다른 글

[Java] 다형성 활용하기  (0) 2022.12.19
[Java] 다형성(polymorphism)  (0) 2022.12.15
[Java] 상속에서 클래스 생성과 형 변환  (0) 2022.11.20
[Java] 상속이란?  (0) 2022.11.17
[Java] ArrayList 클래스 사용하기  (0) 2022.11.15