[그린컴퓨터] Server/JAVA(객체 지향 프로그래밍)

메서드 오버라이딩 { 상위 클래스 메서드 재정의하기, VIP 고객 클래스의 제품 가격 계산 메서드 재정의하기, 묵시적 클래스 형 변환과 메서드 재정의, 가상 메서드 }

Ben의 프로그램 2023. 5. 30. 13:50
728x90

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

  • 이전 예제에서 VIP 등급의 고객을 생성할 때 VIP 고객에게 제공하는 할인율과 세일 가격을 어떻게 적용할지 구현하지 않았습니다. 
  • 어떻게 이 기능을 구현할지 해결해 보겠습니다. 
  • 상위 클래스 Customer 에는  calcPrice( ) 라는 메서드를 통해 제품 가격을 계산합니다.
public int calcPrice(int price) {
    bonusPoint += price * bonusRatio;
    return price;
}
  • 이 메서드는 정가를 그대로 지불하도록 구현되어 있습니다. 
  • 그런데 VIP 고객은 정가에서 10% 할인을 받을 수 있습니다. 
  • 이런 경우 VIP 고객 클래스에서는 상위 클래스의 calcPrice( ) 메서드를 그대로 쓸 수 없습니다.
  • 이렇게 상위 클래스에 정의한 메서드가 하위 클래스에서 구현할 내용과 맞지 않을 경우에 하위 클래스에서 이 메서드를 재정의할 수 있습니다. 
  • 이를 메서드 오버라이딩 method overriding 이라고 합니다.
  • 오버라이딩을 하려면 반환형, 메서드 이름, 매개 변수 개수, 매개변수 자료형이 반드시 같아야 합니다.
  • 그렇지 않으면 자바 컴파일러는 재정의한 메서드를 기존 메서드와 다른 메서드로 인식합니다. 

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

  • VIPCustomer 클래스에서 calcPrice( ) 메서드를 재정의해 봅시다. 
public int calcPrice(int price) {
    bonusPoint += price * bonusRatio;
    return price - (int)(price * saleRatio);
}
  • 하위 클래스 VIPCustomer 에서 calcPrice( ) 메서드를 재정의했습니다. 
  • 상위 클래스의 calcPrice( )  메서드와 매개변수의 자료형 및 개수가 같고, 반환형도 int 형으로 같습니다. 따라서 메서드 오버라이딩이 진행될 것을 알 수 있습니다. 
  • 상위 클래스의 메서드를 재정의할 때는 조금 전 실습처럼 메서드 이름을 직접 써도 되지만, eclipse 가 제공하는 기능을 활용해도 좋습니다. Source - Override/Implement Methods 를 눌러서 활용하면 상위 클래스의 메서드 재정의를 손쉽게 할 수 있습니다. 
  • @Overrride 애노테이션은 '이 메서드는 재정의된 메서드입니다'라고 컴파일러에 명확히 알려주는 역할을 합니다.
  • 두 고객을 생성해서 지불하는 가격을 출력해 보는 테스트 프로그램을 만들어보겠습니다. 
public class OverridingTest1 {

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

}

출력값

  • 이순신 고객은 일반 등급이므로 10,000원을 그대로 지불하지만, 김유신 고객은 VIP 등급이므로 10% 할인을 받아 9,000원을 지불하도록 프로그램을 구현했습니다. 

애노테이션(Annotation)이란?

  • 애노테이션 Annotation 은 영어로는 주석이라는 의미입니다. 
  • 자바에서 제공하는 애노테이션은 컴파일러에게 특정한 정보를 제공해 주는 역할을 합니다. 
  • @Override 와 같이 미리 정의되어 있는 애노테이션을 표준 애노테이션이라고 합니다. 
  • 만약 메서드의 선언부가 애노테이션과 다르다면 컴파일 오류가 발생하여 프로그래머의 실수를 막아줍니다. 
Annotation 설명
@Override 재정의된 메서드라는 정보 제공
@FunctionInterface 함수형 인터페이스라는 정보 제공
@Deprecated 이후 버전에서 사용되지 않을 수 있는 변수, 메서드에 사용됨
@SuppressWarnings 특정 경고가 나타나지 않도록 함
  • 주로 사용하는 표준 애노테이션은 위와 같습니다. 

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

  • 다음과 같은 경우에는 어떻게 실행될지 생각해 봅시다. 
public static void main(String[] args) {
    Customer vc = new VIPCustomerExtends(10030, "나몰라", 2000);
    vc.calcPrice(10000);
}
  • 묵시적 형 변환에 의해 VIPCustomer 가 Customer 형으로 변환되었습니다. 
  • 그리고 나서 clacPrice( )  메서드가 호출되었습니다.
  • 여기서 사용된 calcPrice( ) 메서드는 상위 클래스에서 선언된 메서드일까요? 아니면 하위 클래스에서 재정의된 클래스가 실행된 걸까요? 
  • 다음 코드로 확인을 해보겠습니다.
public static void main(String[] args) {
    Customer vc = new VIPCustomerExtends(10030, "나몰라", 2000);
    vc.bonusPoint = 1000;

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

출력값

  • 멤버 변수와 메서드는 선언한 클래스형에 따라 호출됩니다.
  • 그러면 vc.calcPrice(10000) 은 당연히 선언한 클래스형인 Customer 클래스의 calcPrice( )  메서드를 호출해야겠죠. 
  • 그런데 출력 결과를 보니 9,000 원입니다. 
  • VIPCustomer 클래스의 calcPrice( ) 메서드, 다른 말로 재정의된 메서드가 호출되었음을 알 수 있습니다. 
  • 상속에서 상위 클래스와 하위 클래스에 같은 이름의 메서드가 존재할 때 호출되는 메서드는 인스턴스에 따라 결정됩니다. 
  • 따라서 선언한 클래스 형이 아닌 생성된 인스턴스의 메서드를 호출하는 것입니다. (이어지는 가상 메서드에서 배우겠지만, 더 정확히 말하면 재정의된 override 된 메서드가 호출되는 것입니다)
  • 이렇게 인스턴스의 메서드가 호출되는 기술을 '가상 메서드 virtual method' 라고 합니다. 
  • 가상 메서드가 실행되는 원리를 이해하면 왜 vc.calcPrice(10000)이 상위 클래스인 Customer 클래스의 메서드가 아닌 생성된 인스턴스, VIPCustomer 의 메서드를 호출하는지 이해할 수 있습니다. 

가상 메서드 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();
	}

}

출력값

  • 위 코드가 실행되는 과정을 메모리의 관점으로 이해하면 다음과 같습니다.
힙 메모리 스택 메모리 메서드 영역
a1의 num 변수 a1 aaa( ) 메서드 영역
a2의 num 변수 a2  
  args  
  • main 함수가 실행되면 지역 변수는 스택 메모리에 위치합니다.
  • a1, a2 참조 변수가 가리키는 인스턴스는 힙 메모리에 생성됩니다.
  • 메서드의 명령 집합은 메서드 영역(코드 영역)에 위치합니다. 
  • 우리가 메서드를 호출하면 메서드 영역의 주소를 참조하여 명령이 실행니다. 
  • 따라서 인스턴스가 달라도 동일한 메서드가 호출됩니다. 

가상 메서드의 원리

  • 일반적으로 프로그램에서 메서드를 호출한다는 것은 그 메서드의 명령 집합이 있는 메모리 위치를 찹조하여 명령을 실행하는 것입니다. 
  • 그런데 가상 메서드의 경우에는 '가상 메서드 테이블'이 만들어집니다. 
  • 가상 메서드 테이블은 각 메서드 이름과 실제 메모리 주소가 짝을 이루고 있습니다. 
  • 어떤 메서드가 호출되면 이 테이블에서 주소 값을 찾아서 해당 메서드의 명령을 수행합니다. 표로 이해해 보겠습니다.
  • 아래 표는 Customer 클래스의 가상 메서드 테이블입니다.
메서드 메서드 주소
calcPrice (재정의됨) 0xFF00FFAA     -> 메서드 영역 Customer 클래스의 calcPrice() 가리킴
showCustomerInfo (재정의되지 않음) 0x1122333AA  ->  메서드 영역 Customer 클래스의 showCustomerInfo() 가리킴
  • 아래 표는 VIPCustomer 클래스의 가상 메서드 테이블입니다.
메서드 메서드 주소
calcPrice (재정의됨) 0x00335577  ->  메서드 영역 VIPCustomer 클래스의 재정의된 calcPrice( ) 가리킴
showCustomerInfo (재정의되지 않음) 0x1122333AA  -> 메서드 영역 Customer 클래스의 showCustomerInfo() 가리킴
getAgentID (하위 클래스에서 추가된 메서드) 0x8899BB33  ->  메서드 영역 VIPCustomer 클래스의 getAgentID() 가리킴
  • 아래 표는 메서드 영역을 나타냅니다.
메서드 영역
Customer 클래스
 calcPrice( )
Custoer 클래스 
showCustomerInfo( )
VIPCustomer 클래스
재정의된 calcPrice( )
VIPCustomer 클래스
getAgentID( )
  • 위에서 보듯이 calcPrice( ) 메서드는 두 클래스에서 서로 다른 메서드 주소를 가지고 있습니다. 
  • 이렇게 재정의된 메서드는 실제 인스턴스에 해당하는 메서드가 호출됩니다. 
  • 다음 예제를 통해 결과를 살펴보도록 하겠습니다. 
  • Customer 클래스로 인스턴스를 생성한 경우와 VIPCustomer 클래스로 인스턴스를 생성한 경우, VIPCustomer 클래스로 인스턴스를 생성하여 Customer 클래스형으로 형 변환한 경우 각각 얼마를 지불해야 하는지 알아보겠습니다.
	public static void main(String[] args) {
		int price = 10000;
		
		Customer customerLee = new Customer(10010, "이순신"); 
		System.out.println(customerLee.getCustomerName() + " 님이 지불해야 하는 금액은 " + customerLee.calcPrice(price) + "원입니다.");
		
		VIPCustomerExtends customerKim = new VIPCustomerExtends(10020, "김유신", 12345);
		System.out.println(customerKim.getCustomerName() + "님이 지불해야 하는 금액은 " + customerKim.calcPrice(price) + "원입니다.");
		
		Customer vc = new VIPCustomerExtends(10030, "나몰라", 2000);
		System.out.println(vc.getCustomerName() + " 님이 지불해야 하는 금액은 " + vc.calcPrice(10000) + "원입니다.");
		
	}
  • Customer 형으로 선언하고 Customer 인스턴스를 생성하면 Customer 의 메서드가 호출됩니다. 
  • 따라서 customerLee 가 지불해야 할 가격은 할인이 되지 않은 10,000원입니다. 
  • VIPCustomer 로 생성한 customerKim 은 당연히 할인된 9,000원을 지불합니다. 
  • VIPCustomer 로 생성하고 Customer 형으로 변환한 vc 는 원래 Customer 형 메서드가 호출되는 것이 맞지만, 가상 메서드 방식에 의해 VIPCustomer 인스턴스의 메서드가 호출되어 할인 가격 9,000원이 출력됩니다. 

가상 메서드 정리

  • 정리해 보겠습니다. 
vc.calcPrice( );    ->  calcPrice( ) 가 재정의 안 된 경우 호출 : Customer 클래스의 calcPrice( )
                    ->  calcPrice( ) 가 재정의 된 경우 호출 : VIPCustomer 클래스의 재정의한 calcPrice( )
  • vc.calcPrice( ) 가 호출되면, 2가지 경우로 실행이 나뉘게 됩니다.
    첫 째, calcPrice 메서드가 하위 클래스에서 재정의가 된 경우, 자료형의 메서드가 호출되는 것이 아니라 생성된 인스턴스의 메서드가 호출됩니다. 
    둘 째, calcPrice 메서드가 하위 클래스에서 재정의가 되지 않은 경우, 자료형의 메서드가 호출됩니다. 
  • 자바의 모든 메서드는 가상 메서드입니다.