[JAVA 객체지향프로그래밍] 오버라이딩과 가상메서드

    반응형

     

    이 글은 패스트 캠퍼스 한번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 강의를 듣고 공부한 내용을 바탕으로 정리한 글입니다.


     

     

     

     

    1. 메서드 재정의(overriding)

    오버라이딩이란, 상속 관계에 있는 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 같은 시그니처를 갖는 메서드로 다시 정의하는 것입니다.

    cf. 오버로딩은 서로 다른 시그니처(함수 정의 부분)를 갖는 메서드들을 하나의 이름을 정의하는 것입니다. 

    자식 클래스는 부모 클래스의 private 멤버를 제외한 모든 메서드를 상속받는데, 상속받은 메서드를 그대로 사용할 수도 있고, 재정의하여 사용할 수도 있습니다.

    * 오버라이딩의 조건 (참고: http://www.tcpschool.com/java/java_inheritance_overriding)

    • 메서드의 선언부(함수 시그니처)는 기존 메서드와 완전히 같아야 합니다.
    • 부모 클래스의 메서드보다 접근 제어자를 더 좁은 범위로 변경할 수 없습니다.

    일반 고객을 상속받는 VIP 고객의 예시로 오버라이딩을 구현해보겠습니다.

    public class Customer {
    
    	...
        
        public int calcPrice(int price) {
            this.bonusPoint += price * bonusRatio;
            return price;
        }
    
    }
    public class VIPCustomer extends Customer {
    	
        ...
        
        @Override
        public int calcPrice(int price) {
            bonusPoint += price * bonusRatio;
            price -= (int)(price * salesRatio);
            return price;
        }
    }

    VIPCustomercalcPrice() 메서드를 재정의하고 있습니다. 오버라이딩을 할 때에는 @Override 어노테이션을 명시해줍니다. 어노테이션은 문법적으로 디텍션되지 않고, @Override는 컴파일러에게 오버라이딩된 메서드라고 알려주는 역할을 합니다.(이외에도 많은 어노테이션이 있습니다.)

    Customer 클래스는 salesRatio가 없고, VIPCustomer 클래스는 salesRatio가 0.1입니다. 메서드를 재정의하여 VIP 고객에게만 할인을 적용하도록 구현하였습니다.

    +) 인텔리제이에서는 cmd+n으로 오버라이드할 메서드를 선택하여 코드를 자동으로 만들 수 있습니다.

    public class CustomerTest {
        public static void main(String[] args) {
            Customer customerLee = new Customer(1001, "Lee");
            customerLee.setBonusPoint(1000);
            int price = customerLee.calcPrice(1000);
            System.out.println(customerLee.getCustomerName() + ": " + price);
            
            VIPCustomer customerKim = new VIPCustomer(1002, "Kim");
            customerKim.setBonusPoint(50000);
            price = customerKim.calcPrice(1000);
            System.out.println(customerKim.getCustomerName() + ": " + price);
    
            Customer customerPark = new VIPCustomer(1000, "Park");
            System.out.println(customerPark.getCustomerName() + ": " + customerPark.calcPrice(1000));
        }
    }

    메인함수에서 Customer 인스턴스와 VIPCustomer 인스턴스, 그리고 업캐스팅된 인스턴스를 각각 만들어서 확인해보면 VIPCustomer 인스턴스와 업캐스팅된 인스턴스에 재정의된 메서드가 적용된 것을 확인할 수 있습니다. 

    이때, 업캐스팅된 인스턴스는 자식 클래스의 인스턴스이지만, 타입은 부모 클래스이기 때문에, 부모클래스의 멤버 변수와 메서드에만 접근할 수 있는데, 왜 부모 클래스에서 정의된 메서드가 아닌 자식 클래스에서 재정의된 메서드가 호출되는 것일까요? 그 이유는 가상 메서드 때문입니다.

     

    2. 메서드 호출과 실행 원리

    가상메서드에 대해 공부하기 전에 메서그다 어떻게 호출되고 실행되는지부터 간단하게 살펴보겠습니다.

    우선, 프로그램이 로드될 때의 메모리 위에 어떻게 올라가는지 봅시다. 프로그램이 메모리에 로드될 때에는 크게 두 부분으로 로드되는데, 코드 영역데이터 영역으로 구분되어 로드됩니다. 메서드는 코드이므로 메모리상의 코드 세그먼트에 로드되고, global 변수와 static 변수는 데이터영역에 로드됩니다.

    • 메서드의 명령어 set은 코드 세그먼트에 위치하고, 메서드의 이름은 그 주소값을 나타냅니다.
      메서드는 함수의 일종으로 메서드(함수)의 이름은 주소값을 나타냅니다. 메서드는 명령어의 set이고 프로그램이 로드되면 메서드 영역(코드 영역)에 명령어 set이 위치하게 되며, 이때의 명령의 set이 위치한 메모리 주소값이 메서드의 이름에 할당되게 됩니다. 그리고 메서드가 호출되면 명령어 set이 있는 주소를 찾아 명령어가 실행됩니다.
    • 따라서 다른 인스턴스라도 같은 메서드는 동일한 주소에 위치한 명령어 set이 실행됩니다.
      메서드에서 사용하는 변수들은 스택 메모리 또는 힙 메모리에 위치합니다. 이는 각각의 변수마다 다른 메모리를 할당받아 따로따로 생성이 됩니다. 하지만 메서드는 메서드는 변수가 달라도 동일한 코드가 호출됩니다.  따라서 다른 인스턴스라도 같은 메서드의 코드는 같으므로 같은 메서드가 호출됩니다. 메서드의 명령어 set이 위차한 메모리 주소는 같으니까요! 따라서 인스턴스가 생성되면 변수는 힙 메모리에 따로 생성되지만, 메서드 명령어 set은 처음 한번만 로드됩니다.

     

    3. 가상 메서드의 원리

    위에서 보았듯이 클래스로부터 인스턴스가 만들어지면, 멤버 변수는 힙 메모리에 만들어지고, 메서드는 프로그램이 처음 로드될 때 생성된 코드 세그먼트에 위치합니다.(즉, 인스턴스가 생성될 때마다 새롭게 생성되는 게 아니죠!) (메서드에서 사용하는 지역 변수는 스택에 위치합니다.)

    간단히 나타내면 아래와 같습니다. 객체는 위의 테스트 코드에서 만든 객체들로 나타내었습니다. customerLeeCustomer 인스턴스이고, customerKimVIPCustomer 인스턴스, customerParkVIPCustomer() 생성자로 만든 Customer 객체입니다.(업캐스팅된 객체)

    이때, 하나의 객체는 하나의 가상 메서드 테이블을 가집니다. 하지만 메서드는 코드 세그먼트에 instruction set으로 한 번씩만 로드되기 때문에 같은 메서드는 같은 메서드 주소를 갖습니다. 따라서 customerPark은 VIPCustomer 생성자로 만들어지기 때문에 오버라이딩된 VIPCustomer의 calcPrice 메서드의 주소를 가지고 있게 되는 것입니다.

     

    반응형

    댓글