[소.자.바] 4.5 Inheritance와 Overriding(완료)

2019. 12. 9. 15:40더 쉽게 쓴 자바(소.자.바)19.11.07

*본 블로그는 개인 공부용으로 해당 내용은 자북의 '최영관'님이 저자이신 소설같은 자바를 읽고 정리한 내용으로써 문제가 있을 시 모두 내리도록 하겠습니다.

 

4.5.1 메소드의 재정의란?

 

 객체지향적 개념에서 Overloading만큼이나 중요하면서도 상속의 개념에서 절대 빼놓을 수 없는 것이 바로 Overriding이다. Overriding 즉, 메소드 재정의는 말 그대로 메소드를 다시 재정의하는 행위를 말한다.

이 장에서는 아버지(의 내용을)를  상속받은 아들이 아버지 메소드와 똑같은 메소드를 다시 만들 경우 이를 어떻게 해결하느냐의 문제를 담고(다루고) 있다. Overriding을 개인적으로 아버지 무시하기로 부르고 있는데 그 이유는 아들이 아버지의 메소드를 재정의 할 경우 기존에 정의되었던 아버지의 메소드(내용을)를 완전히 무시하는 경향이 있기 때문이다.

그럼 이제 메소드 재정의에 대해서 자세히 알아보자.

 

- 66 -

 

4.5.2 상속개념에서의 Overriding

 

 아버지 클래스를 상속받아 아들 클래스를 만들었을 때, 아버지가 가지고 있던 메소드를 아들 클래스가 다시 만들었다고 가정해 보자. 아버지 것은 내 것이고 내 것도 내 것이니 원래는 하나였던 메소드가 이로인해, 완벽(하게)히 똑같은 이름의 메소드가 2개 존재하게 되어 비린다. 아들 클래스가 객체변수를 만들고 이 메소드를 호출한다면 아들 클래스는 순간 당황하게 될 것이다. '아버지 것을 사용할까? 내 것을 사용할까?' 하지만 아들 클래스의 객체는 유유히 자신의 것을 사용한다. 왜냐하면... 자신의 것이 더 소중하니까?! 이렇게 아버지의 메소드는 완전히 무시당하는 것이다. 이것을 우리는 메소드 재정의라고 부른다. 같은 이름의 메소드를 다시 만들 때 상위 클래스의 메소드는 무시당한도록 하는 것이다. 개념은 아주 쉽다. 하지만 여기서 발생하는 약간의 난해한 문제점들이 있다. 좀 이상하다고 도 할 수 있는 그런 개념이니 주의해서 관찰해보도록 하자. 우선, 메소드의 재정의에서 가장 표준이 되는 예를 만들어 메소드 재정의에 대한 개념부터 파악해 보도록 하자.

 

OverrideTest.java                                       //상속에서  Override Method를 테스트하기 위한 예제

  class FaFa{

    public void print(){

      System.out.println("난 할아버지다");

    }

  }

 

  class Baby extends FaFa{

    public void print(){

      System.out.print("난 아버지다");

    }

 

  }

 

  public class OverrideTest{

   public static void main(String[] args){

     FaFa fafa = new FaFa();

            fafa.print();

     Baby baby = new baby();

            baby.print();

     FaFa faba = new Baby();

            faba.print();

   }

  }

 

C:\examples\4.Class for Basic Java>javac OverrideTset.java

C:\examples\4.Class for Basic Java>java  OverrideTset

난 할아버지다

난 아버지다

난 아버지다

 

위의 예제를 통하여 객체 3개를 생성하였다. 객체 생성 부분을 다시 살펴보도록 하자.

■ FaFa fafa   = new FaFa();

■ Baby baby = new Baby();

■ FaFa faba  = new Baby();

 

- 67 -

 

 첫번째는 아주 쉽다. FaFa 클래스가 자신의 메모리(만큼을)를 생성하고 있다. fafa 객체의 print 메소드 호출은 당연히 FaFa 클래스의 메소드가 될 것이다. 두번째의 경우,  Baby 클래스는 Baby 클래스(만큼을)를 생성하고 있다. 그리고 Baby 클래스는 baby  객체를 만들었으며 Overriding 개념을 적용시킨다면 아버지의 print는 무시되어지고 자신의 print가 호출 되어질 것이다. 이것은 이 절의 중심 주제이다. 여기서 잠깐! 참고하나 하자면 하나의 파일 내에 두개의 클래스가 존재할 때엔 메인 메소드를 가지고 있는 클래스가 파일 이름의 주인이 된다. 그리고 public 클래스는 하나만 존재할 수 있다.

 

 Overriding의 개념이 너무 단순하다는 것이 약간 의심스럽지 않은가? Baby 클래스가 baby 객체를 만들었을 때 자신의 것을우선하느냐 아버지의 것을 우선하느냐의 문제에서 자신의 것을 우선한다는 것은 기정사실처럼 이치에 딱 들어맞는다(만약 부모님이  가게를 운영하시다가 나에게 그 경영을 물려주신다면 나는 이전과 동일하게 가게를 꾸려가게 될까? 사람이라면 대부분 경영을 물려받자 마자 마음에 들지 않았던 것들을 자신이 생각했던 스타일 바꾸어 나갈 것이다). 그런데 여기서 세번재 객체 생성을 한번 살펴보자.

 아주 황당무계한 일이 벌어지고 있다. 데이터 타입은 아버지의 것을 사용하곤 메모리는 아들 것을 사용하고 있다. 정리해 보면 아버지의 데이터 타입 객체변수에 아들의 메모리를  넣는 형식이 된다. 우리는 이것을 업 캐스팅이라고 부른다. 그렇다면 이렇게 만들어진 아들의 메모리를 가진 아버지의 데이터 타입 객체변수는 아버지의 특성이 강할까? 아니면 아들의 특성이 강할까? 메소드가 재정의 되었다면 아버지이지만(면서) 아들의 메소드를 호출할 수 있다. 그렇기 때문에 아버지의 객체변수로 print를 호출 했을 때 아들의 메소드가 호출된다. 그 이외에는 완벽히 아버지의 역할을 수행한다. 그래서 위 예제의 결과는 아버지 객체변수가 아들의 메소드를 호출한 것이다. 전체적인 구조는 아래의 그림과 같다.

 

그림.상속구조

 

 문제는 "FaFa faba = new Baby()"이 어떠한 장점을 갖고 있는가에 있다. 많은 장점이 있는데... 사실, 여기에는 엄청난 장점이 있다. 이 업 캐스팅의 느낌을 가지고 있다면 여러분은 더 이상 배울 것이 없다. 하산해도 된다는 소리다. 업 캐스팅은 덩치가 너무 크기 때문에 앞으로 나올 장에서 다시 설명하도록 하겠다. 여기서는 이렇게 정의만 내려놓고 가겠다.

 

■ 아버지의 이름으로 아들의 메모리를  참조한다.

■ 아버지라 하더라도 메소드의 재정의가 사용되었다면 아들의 메소드를 호출한다.

■ 메소드 재정의 이외에는 완벽한 아버지의 역할을 한다.

 

- 68 -

 

 이 개념은 분명한 C++의 개념으로  Visual C++에서도 이 개념을 빼버린다면 VC++라 할 수 없다. 보통 ANSI C++에서는 가상 메소드라고 부르는데, 물론 VisualC++와 C# 에서도 이 visual 메소드는 사용된다. 하지만 자바에서는 특별한 설명을 붙이고 있지 않기 때문에 더 혼동되는 요소들이 있다.

 

4.5.3 메소드 재정의에 대한 느낌

 

 아버지의 메소드를 아들이 다시 만들었다면 이것은 아들이 아버지의 특징을 더욱 발전시키거나 아니면 아들만의 즉, 자신만의 특성을 가진 것으로 다시 만든 것이다. 당연히 아들은 아버지의 것보다 자신의 것을 사용할 것이다. 이것은 아버지의 모든 것을 상속한다는 특수한 방법을 통해서 아들은 아버지의 것을 모두 받아들이고 필요한 부분은(만) 개선하겠다는 의미를 담고 있다. 메소드 재정의란 이렇게 아주 유용하게 사용할 수 있으며 자바에서 메소드 재정의를 빼면 시체라 할 수 있을 정도로 아주 일반적인 프로그래밍 기법이다.

 

4.5.4 아버지 무시의 느낌

 

 할아버지, 아버지 그리고 아들 모두 print 메소드를 가지고 있다고 생각해보자. 그렇게 되면 아버지는 할아버지의 print와 아버지의 print 이 두 개의 메소드를, 아들은 이 둘을 포함한 자신의 것까지 총 3개를 가지게 된다. 이때 아들의 입장에서 본다면 과연 어떤 것(메소드)을 사용하게 될까? 아버지는 할아버지의 print를 무시한다. 그리고 아들은 다시 아버지의 print를 무시하게 되고. 아들은 전체 3개의 print를 가지고 있지만 아버지 이전의 것들은 통으로 하나의 조상으로 취급하기 때문에 print는 2개가 있는 것으로 취급된다.

■ 할아버지의 print 

■ 아버지의 print 

■ 아들의 print 

 

 아버지가 모든 그 상위 레벨들의 정보를 전부 가지고 있기 때문에 아들은 아버지 자체를 하나의 조상으로 취급 하게 된다. 그 이전의 모든 정보나 특징들은 아버지가 (알아서, 사람으로 치자면 집안의 노하우?) 다 가지고 있는 것이다. 처음 이러한 상속의 개념을 접하게 되면, 할어버지의 할아버지는 어떻게 처리할 것인지 당연스레 의심이 가게 마련이다. 하지만 아들의 입장에서 바라보면 아버지가 모든 정보를 다 알아서 처리하기 때문에 아버지 상위에 있는 조상들의 정보를 정리해 주기 때문에 아들은 아버지만(을) 생각하면 된다. 즉 아들은 아버지의 print만 생각하면 되는 것이다. 실제로는 3개의 print가 존재하지만 아들의 입장에서는 print가 2개 존재하는 것이 되어버리는 것이다. 아버지의 print와 자신의 print 즉, 아버지의 print를 무시하는 것이다. 

만약 아래와 같게 된다면, 즉 아버지의 print가 없다면 어떻게 되는 것일까? 잘 생각해 보자.

 

■ 할아버지의 print

■ 아버지의 print가 없음

■ 아들의  print

 

 이는 아래와 같은 구조로 비교하여 나타낼 수 있다.

 

 -69 -

 

그림.상속관계 3단계 정의

 아버지는 할아버지를 상속 받았기 때문에 할아버지의 print는 곧 아버지의 print가 된다. 그렇기 때문에 할아버지의 print 메소드가 아버지의 print 메소드가 되는 것이다. 그래서 아버지의 print를 무시하게 되지만 실제적으로는 할아버지의 print를 무시하는 것이 된다(아버지의 print가 곧 할아버지의 print이므로 아버지를 무시하면 할아버지의 것이 무시 되는 것).

 

4.5.5 결론

 

 상속의 개념에서 적용되는 Overriding은 클래스의 재사용이라는 측면에서 아주 유용하게 사용할 수 있다. 이는 아버지 클래스에서 원하는 부분만을 가져다 개선하여 다시 사용하겠다는 의미이기 때문이다. Overriding 자체의 개념은 아주 쉽게 이해하고 넘어 갈 수 있다. 비록 아직 완벽하게 설명되지는 않았으나  "아버지의 이름으로 아들을 가르킨다"라는 Upcastintg의 개념은 Overriding 개념의 최고봉이라고 할 수 있다. 이 절에서는 간단히 "이렇게 한다."라는 정도만 알아두고 이 개념이 다시 나오게 되면 그 때 철저히 분해를 해보도록 하자. 

 그런데, 아버지 무시하기 즉, 메소드를 재정의 했을 때 아버지를 완벽하게 무시하게 된다면 아버지의 메소드를 사용하고 싶을 때는 문제가 발생하게 된다. 무시해버렸기 때문에 아버지의 메소드를 호출하고 싶을 때는 방법이 없어져 버리게 되는 것이다. 이것을 해결하기 위한 방법으로 자바에서는 super라는 아버지의 참조값을 두어 해결하고 있다. 이렇게 아들은 super라는 아버지의 참조값으로 아버지의 메소드를 호출 할 수 있게 되었다. 그럼 이제  this의 개념과  super의 개념에 대해서 알아보도록 하자.