JAVA Basic) 메서드 오버라이딩(method overriding)
Programming/Java 기초

JAVA Basic) 메서드 오버라이딩(method overriding)

반응형

 

목차


     

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

    지난 포스팅에서 새로운 등급을 만들면서 VIP 고객에게 제공하는 할인율과 세일 가격을 적용, 구현하지 않았다.

    상위 클래스인 Customer에는 제품 가격을 계삲는 calcPrice()메서드가 이미 정의되어 있다.

    public int calcPrice(int price){
    	bonus += price * bonusRatio;
        return price;
    }


    이 메서드는 정가를 그대로 지불하게 된다.
    근데 VIP고객은 정가에서 10%할인을 받을 수 있도록 했다. 이 경우 상위 클래스의 calcPrice()메서드를 그대로 쓸 수 없을 것이다.(할인율이 다르기 때문)
    이렇게 상위 클래스에서 정의한 메서드가 하위 클래스에서 구현할 내용과 다를 경우에 하위 클래스에서 이 메서드를 재정의 하는 것'메서드 오버라이딩(method overriding)'이라고 한다.
    오버라이딩을 하려면 반환형, 메서드 이름, 매개변수 개수, 매개변수 자료형이 반드시 같아야 한다. 그렇지 않으면 자바 컴파일러는 재정의한 메서드를 기존 메서드와 다른 메서드로 인식하기 때문이다.

     

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

    VIPCustomer 클래스의 calcPrice() 메서드 재정의

    package inheritance;
    
    public class VIPCustomer extends Customer{
    	private int agentID;	// 담당 전문 상담원
    	double saleRatio;		// 할인율
    ...
    	@Override
    	public int calcPrice(int price) {
    		bonusPoint += price * bonusRatio;		// 보너스 포인트 적립
    		return price - (int)(price * saleRatio);//할인된 가격을 계산하여 반환
    	}// 재정의한 메서드
    ...

    하위 클래스 VIPCustomer에서 calcPrice()메서드를 재정의했다. 상위 클래스의 calcPrice()메서드와 매개변수의 자료형 및 개수가 같고, 반환형도 int형으로 같습니다. return~saleRatio); 문장은 할인율을 계산하여 정가에서 뺀 후 세일 가격을 반환(return)합니다.

    오른쪽 마우스 버튼 - source - override / Implement Method…

    상위 클래스의 메서드를 재정의할 때는 조금 전 실습처럼 메서드 이름을 직접 써도 되고, 이클립스의 기능을 활용할 수도 있다.
    코드에서 [오른쪽 마우스 버튼 - source - override / Implement Method…]를 누르면 사용할 수 있다.

    상위 클래스 Customer의 메서드 중 재정의할 메서드를 선택할 수 있다. calcPrice(int)를 선택하고 [OK]를 누르면 자동으로 재정의할 메서드의 코드가 VIPCustomer클래스에 생성된다.

     

     

     

    @Override 애노테이션'이 메서드는 재 정의된 메서드이다'라고 컴파일러에 명확히 알려주는 역할을 한다.


    ※ 애너테이션이란?

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

    이렇게 미리 정의된 애노테이션
    '표준 애노테이션'
    이라고 한다.

     

     

     


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

    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)+"입니다.");
    	}
    }

    run:

    이순신 고객은 일반 등급이므로 정가 10,000원을 그대로 지불.
    김유신 고객은 VIP 등급으로 10%할인받아 9,000원을 지불.

     

     

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

    다음 코드가 있다.

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

    묵시적 형 변환에 의해 VIPCustomer가 Customer형으로 변환되었다. 그리고 calcPrice()메서드가 호출되었다.
    calcPrice()는 하위 클래스에서 재정의된 메서드이며, Customer클래스와 VIPCustomer클래스에 모두 존재한다.
    그럼 vc.calcPrice(10000)은  어떤 클래스의 메서드를 호출할까?

    테스트를 해보자.

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

    run:

    멤버 변수와 메서드는 선언한 클래스 형에 따라 호출된다.
    그러나 출력 결과를 보니 9,000원으로 재정의된 VIPCustomer의 calcPrice()메서드가 호출되었다.

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

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

     

    <가상 메서드>

    자바의 클래스는 '멤버 변수''메서드'로 이루어져 있다.
    클래스를 생성하여 인스턴스가 만들어지면 '멤버 변수는 힙 메모리에 위치'한다.

    그럼 메서드는 어디에 위치할까? 변수가 사용하는 메모리와 메서드가 사용하는 메모리는 다르다.
    변수는 인스턴스가 생성될 때마다 새로 생성되지만, 메서드는 실행해야 할 명령 집합이기 때문에 인스턴스가 달라도 같은 로직을 수행한다.
    즉 같은 객체의 인스턴스를 여러 개 생성한다고 해서 메서드도 여러 개 생성되지가 않는다.

    package virtualfuntion;
    
    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가 가리키는 인스턴스는 힙 메모리에 생성된다.
    ---여기까지 우리가 학습한 내용---

    메서드의 명령 집합은 메서드 영역(코드영역)에 위치한다.
    우리가 메서드를 호출하면 메서드 영역의 주소를 참조하여 명령이 실행.
    따라서 인스턴스가 달라도 동일한 메서드를 호출.

     

    가상 메서드의 원리

    일반적으로 프로그램에서 메서드를 호출한다는 것은 그 메서드의 명령 집합이 있는 메모리 위치를 참조하여 명령을 실행하는 것.
    그런데 '가상 메서드'의 경우에는 '가상 메서드 테이블'이 생성된다.
    가상 메서드 테이블은 각 메서드 이름과 실제 메모리 주소가 짝을 이루고 있다.
    어떤 메서드가 호출되면 이 테이블에서 주소 값을 찾아서 해당 메서드의 명령을 수행한다.

    [Customer, VIPCustomer 클래스의 가상 메서드 테이블]

    그림에서 보듯 calcPrice()메서드는 두 클래스에서 서로 다른 메서드 주소를 가지고 있다.
    이렇게 재정의된 메서드는 실제 인스턴스에 해당하는 메서드를 호출하게 된다. showCustomerInfo()와 같이 재정의되지 않은 메서드는 메서드 주소가 같으며 상위클래스의 메서드를 호출하게 된다.

    다음 예제를 보자.

    package inheritance;
    
    public class Overriding3 {
    	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);
    		//VIPCustomer 인스턴스를 Customer형으로 변환
    		System.out.println(vc.getCustomerName() + "님이 지불해야 하는 금액은" + vc.calcPrice(price) + "원 입니다");
    	}
    }

    run:

    가격이 10,000원인 상품을 Customer, VIPcustomer 클래스,
    그리고 VIPCustomer클래스로 인스턴스를 생성하여, Customer클래스형으로 변환한 경우를 비교해보았다.

     

    Customer형 - Customer인스턴스 -> Customer메서드 호출. (10,000원)
    VIPCustomer형 - VIPCustomer인스턴스 -> VIPCustomer메서드 호출. (9,000원)
    Customer형 - VIPCustomer인스턴스 -> VIPCustomer메서드 호출. (9,000원)
    (가상 메서드 방식에 의해 재정의된 메서드를 호출)

    메서드의 재정의 여부에 따른 vc인스턴스의 메서드 호출

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


    [Do it! 자바 프로그래밍 입문] 도서로 공부하며 정리한 글입니다.

    반응형