본문 바로가기

프로그래밍 언어/JAVA

JAVA 입문 - 메서드 오버라이딩

지난 글: [프로그래밍 언어/JAVA] - JAVA 입문 - 상속에서 클래스 생성과 형 변환

 

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

더보기

지난 글에서 만들었던 Customer 클래스에는 제품 가격을 계산하는 calcPrice( ) 메서드가 구현되었었다. 하지만 VIP 고객에게 제공하는 할인율과 세일 가격을 메서드로 구현하지는 않았었다. 그 문제를 해결하려 VIPCustomer에도 calcPrice( ) 메서드를 작성하려는데 상위 클래스의 calcPrice( ) 메서드와는 다른 내용이 수행되어야 한다. 이렇게 사우이 클래스에서 정의한 메서드가 하위 클래스에서 구현할 내용과 맞지 않을 경우에 하위 클래스에서 해당 메서드를 재정의할 수 있다. 이를 메서드 오버라이딩(method overriding)이라고 한다. 오버라이딩은 반환형, 메서드 이름, 매개변수 개수, 매개변수 자료형이 반드시 같아야 하며, 그렇지 아니한 경우 JAVA 컴파일러는 재정의한 메서드를 기존 메서드와 다른 메서드로 인식한다.

 

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

하위 클래스인 VIPCustomer 클래스에 calcPrice( ) 메서드를 재정의 했다. 상위 클래스의 calcPrice( ) 메서드와 반환형, 메서드 이름, 매개변수 개수, 매개변수 자료형이 같다.

 

상위 클래스의 메서드를 재정의할 때는 조금 전 사진처럼 메서드 이름을 직접 써도 되고, 이클립스의 기능을 활용하는 방법도 있다. 마우스 오른쪽 버튼을 클릭하고 Source - Override/implement Methods를 클릭하면 사진과 같은 창이 뜬다.

위 창에서 상위 클래스의 메서드중 재정의할 메서드를 선택할 수 있다.  사진에서는 위에 이미 calcPrice( ) 메서드의 재정의가 되었기에 선택 창에 나타나지 않는다. @Override 애노테이션은 '이 메서드는 재정의된 메서드입니다'라고 컴파일러에게 명확히 알려 주는 역할이다.

 

애노테이션(Annotation)

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

재정의된 calcPrice( ) 메서드 테스트

두 고객을 생성해서 지불하는 가격을 출력해보는 테스트 프로그램을 구현해보자.

출력 결과

개발 초보 고객은 일반 고객이므로 정가 10,000원을 그대로 지불하는 반면 간호사 고객은 VIP 등급이므로 10% 할인을 받아 9,000원을 지불한다.

 

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

더보기

이는 묵시적 형 변환에 의해 VIPCustomer가 Customer형으로 변환되었다. 그리고 calcPrice( ) 메서드를 호출했다. calcPrice( ) 메서드는 상위 클래스와 하위 클래스 모두에 존재한다. 만약 위와 같은 경우라면 vc.calcPrice(10000)은 어떤 클래스의 메서드를 호출할까? 

 

클래스 형 변환과 재정의 메서드 호출 테스트

출력 결과

멤버 변수와 메서드는 선언한 클래스형에 따라 호출된다. 그래서 나도 당연히 vc.calcPrice(10000) 메서드는 Customer 클래스의 메서드를 호출할 줄 알았다. 하지만 출력 결과에는 VIPCustomer 클래스의 메서드가 호출되어 결과 값이 나왔다. 상속에서 상위 클래스와 하위 클래스에 같은 이름의 메서드가 존재할 때 호출되는 메서드는 인스턴스에 따라 결정된다고 한다.즉 선언한 클래스형인 Customer가 아닌 생성된 인스턴스의 메서드를 호출하는 것이다. 이처럼 인스턴스의 메서드가 호출되는 기술을 '가상 메서드(virtual method)'라고 한다. 가상 메서드가 실행되는 원리를 이해하면 vc.calcPrice(10 000)가 호출하는 메서드가 왜 Customer 클래스의 것이 아닌 VIPCustomer 클래스의 것인지 이해할 수 있다고 한다.

 

가상 메서드

더보기

JAVA의 클래스는 멤버 변수와 메서드로 이루어져 있다. 클래스를 생성하여 인스턴스가 만들어지면 멤버 변수는 힙 메모리에 위치하지만 메서드는 메서드 영역(코드 영역)에 위치한다. 변수는 인스턴스가 생성될 때마다 새로 생성되지만, 메서드는 실행해야 할 명령 집합이기에 인스턴스가 달라도 같은 로직을 수행한다. 즉 같은 객체의 인스턴스를 여러 개 생성한다고 해서 메서드도 여러 개 생성되지 않는다는 것이다.

 

가상 메서드의 원리

일반적으로 프로그램에서 메서드를 호출한다는 것은 메서드의 명령 집합이 있는 메모리 위치를 참조하여 명령을 실행하는 것이지만 가상 메서드의 경우에는 '가상 메서드 테이블'이 만들어진다. 가상 메서드 테이블은 각 메서드 이름과 실제 메모리 주소가 짝을 이루고 있다. 어떤 메서드가 호출되면 이 테이블에서 주소 값을 찾아서 해당 메서드의 명령을 수행한다. Customer 클래스의 가상 메서드 테이블과 VIPCustomer 클래스의 가상 메서드 테이블에서 재정의된 calcPrice( ) 메서드는 서로 다른 메서드 주소를 갖고 있다. 이처럼 재정의된 메서드는 실제 인스턴스에 해당하는 메서드가 호출된다. showCustomerInfo( )처럼 재정의되지 않은 메서드인 경우 메서드 주소가 같으며 상위 클래스의 메서드가 호출된다. 테스트를 통해 가격이 10,000원인 상품을 Customer 클래스의 인스턴스와 VIPCustomer 클래스의 인스턴스, 마지막으로 VIPCustomer 클래스로 인스턴스를 생성하여 Customer 클래스형으로 형 변환한 경우 각각 얼마를 지불해야 하는지 알아보자.

 

클래스형에 기반한 지불 금액 테스트

출력 결과

7행처럼 Customer형으로 선언하고 Customer 인스턴스를 생성하면 Customer 클래스의 메서드가 호출된다. 11행에서 VIPCustomer형으로 선언하고 VIPCustomer 인스턴스를 생성하면 VIPCustomer 클래스의 메서드가 호출된다. 마지막으로 15행에서 VIPCustomer로 생성하고 Customer형으로 변환한 customer3는 원래라면 Customer형 메서드가 호출되어야 하지만 가상 메서드 방식에 의해 VIPCustomer 인스턴스의 메서드가 호출되었다. 

 

마지막으로 한 번 더 정리하자면 상위 클래스(Customer)에서 선언한 calcPrice( ) 메서드와 하위 클래스(VIPCustomer)에서 이를 재정의한 상태에서 하위 클래스 인스턴스(customer3)가 상위 클래스형으로 변환 되었다. 이때 customer3.calcP rice( )가 호출되면 변수를 선언할 때 사용한 자료형(Customer)의 메서드가 호출되는 것이 아니라 생성된 인스턴스(VIPCustomer)의 메서드가 호출된다. 이를 가상 메서드라고 하며 JAVA의 모든 메서드는 가상 메서드이다.

 

참고 서적: 자바 프로그래밍 입문 - 박은종