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 메서드가 하위 클래스에서 재정의가 되지 않은 경우, 자료형의 메서드가 호출됩니다. - 자바의 모든 메서드는 가상 메서드입니다.
'[그린컴퓨터] Server > JAVA(객체 지향 프로그래밍)' 카테고리의 다른 글
다형성 활용하기 { 배열과 다형성, 배열과 다형성 활용하기, 상속은 언제 사용할까 } (0) | 2023.05.31 |
---|---|
다형성 { 다형성이란, 다형성의 장점 } (0) | 2023.05.31 |
상속에서 클래스 생성과 형 변환 { 하위 클래스가 생성되는 과정, 부모를 부르는 예약어 super, 상위 클래스로 묵시적 클래스 형 변환 } (0) | 2023.05.29 |
상속이란 { 상속이란, 클래스의 상속, 상속을 사용하여 고객 관리 프로그램 구현하기 } (0) | 2023.05.29 |
배열 응용 프로그램 { Student 클래스 구현하기, 테스트 클래스 구현 } (0) | 2023.05.25 |