Java - 상속이란?

김영한님의 Java 강의로 기초다지기 - 상속이란?

Posted by Warner on December 20, 2023

상속

extends1.png

전기차(ElectricCar)와 가솔린차(GasCar)를 만들었다. 전기차는 이동(move()), 충전(charge()) 기능이 있고, 가솔린차는 이동(move()), 주유(fillUp()) 기능이 있다.

전기차와 가솔린차는 자동차(Car)의 좀 더 구체적인 개념이다. 반대로 자동차(Car)는 전기차와 가솔린차를 포함하는 추상적인 개념이다. 그래서인지 잘 보면 둘의 공통 기능이 보인다. 바로 이동(move())이다.
전기차든 가솔린차든 주유하는 방식이 다른 것이지 이동하는 것은 똑같다. 이런 경우 상속 관계를 사용하는 것이 효과적이다.

상속 관계

상속은 객체 지향 프로그래밍의 핵심 요소 중 하나로, 기존 클래스의 필드와 메서드를 새로운 클래스에서 재사용하게 해준다. 이름 그대로 기존 클래스의 속성과 기능을 그대로 물려받는 것이다. 상속을 사용하려면 extends 키워드를 사용하면 된다. 그리고 extends 대상은 하나만 선택할 수 있다.

용어 정리

  • 부모 클래스 (슈퍼 클래스) : 상속을 통해 자신의 필드와 메서드를 다른 클래스에 제공하는 클래스
  • 자식 클래스 (서브 클래스) : 부모 클래스로부터 필드와 메서드를 상속받는 클래스

extends2.png 전기차와 가솔린차가 Car를 상속 받은 덕분에 electricCar.move() , gasCar.move() 를 사용할 수 있다.

  • 자식이 부모의 기능을 물려 받아서 사용할 수 있다.
  • 반대로 부모 클래스는 자식 클래스에 접근할 수 없다.

단일 상속 참고로 자바는 다중 상속을 지원하지 않는다. 그래서 extend 대상은 하나만 선택할 수 있다. extends3.png

다중 상속 허용시 발생하는 문제

  • 다이아몬드 문제 발생
    • AirplaneCar 입장에서 move()를 호출할 때 어떤 부모의 move()를 사용해야 할지 애매한 문제가 발생한다.
  • 계층 구조 복잡

상속과 메모리 구조

상속 관계를 객체로 생성할 때 메모리 구조

ElectricCar electricCar=new ElectricCar(); // Car 상속

extends4.png

  • new ElectricCar()를 호출하면 ElectricCar 뿐만 아니라 상속 관계에 있는 Car 까지 함께 포함해서 인스턴스를 생성
  • 참조값은 x001로 하나이지만 실제로 그안에서는 Car, ElectricCar 라는 두가지 클래스 정보가 공존
  • 외부에서 볼때는 하나의 인스턴스를 생성하는 것 같지만 내부에서는 부모와 자식이 모두 생성되고 공간도 구분된다.

extends5.png

  • electricCar.charge()를 호출하면 참조값을 확인해서 x001.charge()를 호출한다.
  • x001 내부에 부모와 자식이 모두 존재
  • 부모의 Car를 통해서 charge()를 찾을지 아니면 ElectricCar 를 통해서 charge() 를 찾을지 선택해야 한다.
  • 이때는 호출하는 변수의 타입(클래스)을 기준으로 선택한다.
  • electricCar 변수의 타입이 ElectricCar 이므로 인스턴스 내부에 같은 타입인 ElectricCar를 통해서 charge()를 호출

extends6.png

  • electricCar.move()를 호출하면 먼저 x001 참조로 이동한다.
  • 내부에는 Car, ElectricCar 두가지 타입이 있다.
  • 호출하는 변수인 electricCar의 타입이 ElectricCar이므로 이 타입을 선택한다.
  • ElectricCar에는 move() 메서드가 없다.
  • 상속 관계에서는 자식 타입에 해당 기능이 없으면 부모 타입으로 올라가서 찾는다.
  • ElectricCar의 부모인 Car로 올라가서 move() 를 찾는다.
  • 부모인 Carmove() 가 있으므로 부모에 있는 move() 메서드를 호출한다.

정리

  • 상속 관계의 객체를 생성하면 그 내부에는 부모와 자식이 모두 생성된다.
  • 상속 관계의 객체를 호출할 때, 대상 타입을 정해야 한다. 이때 호출자의 타입을 통해 대상 타입을 찾는다.
  • 현재 타입에서 기능을 찾지 못하면 상위 부모 타입으로 기능을 찾아서 실행한다. 기능을 찾지 못하면 컴파일 오류가 발생한다.

상속과 메서드 오버라이딩

부모 타입의 기능을 자식에서는 다르게 재정의 하고 싶을 수 있다.
예를 들어서 자동차의 경우 Car.move()라는 기능이 있다. 이 기능을 사용하면 단순히 “차를 이동합니다.” 라고 출력한다. 전기차의 경우 보통 더 빠르기 때문에 전기차가 move()를 호출한 경우에는 “전기차를 빠르게 이동합니다.”라고 출력을 변경하고 싶다.

이렇게 부모에게서 상속 받은 기능을 자식이 재정의 하는 것을 메서드 오버라이딩(Overriding) 이라 한다.

@Override

이 애노테이션은 상위 클래스의 메서드를 오버라이드하는 것임을 나타낸다.

  • 컴파일러는 이 애노테이션을 보고 메서드가 정확히 오버라이드 되었는지 확인한다.
  • 오버라이딩 조건을 만족시키지 않으면 컴파일 에러를 발생시킨다.

extends7.png Car의 move() 메서드를 ElectricCar에서 오버라이딩 했다.

extends8.png

  1. electricCar.move() 를 호출한다.
  2. 호출한 electricCar 의 타입은 ElectricCar 이다. 따라서 인스턴스 내부의 ElectricCar 타입에서 시작한다.
  3. ElectricCar 타입에 move() 메서드가 있다. 해당 메서드를 실행한다. 이때 실행할 메서드를 이미 찾았으므로 부모 타입을 찾지 않는다.

오버로딩(Overloading)과 오버라이딩(Overriding)

  • 메서드 오버로딩 : 메서드 이름이 같고 매개변수(파라미터)가 다른 메서드를 여러개 정의하는 것을 메서드 오버로딩(Overloading)이라 한다. 오버로딩은 번역하면 과적인데, 과하게 물건을 담았다는 뜻이다. 따라서 같은 이름의 메서드를 여러개 정의했다고 이해하면 된다.

  • 메서드 오버라이딩 : 메서드 오버라이딩은 하위 클래스에서 상위 클래스의 메서드를 재정의하는 과정을 의미한다. 따라서 상속 관계에서 사용한다. 부모의 기능을 자식이 다시 정의하는 것이다. 오버라이딩을 단순히 해석하면 무언가를 넘어서 타는 것을 말한다. 자식의 새로운 기능이 부모의 기존 기능을 넘어 타서 기존 기능을 새로운 기능으로 덮어 버린다고 이해하면 된다. 오버라이딩을 우리말로 번역하면 무언가를 다시 정의한다고 해서 재정의라 한다. 상속 관계에서는 기존 기능을 다시 정의한다고 이해하면 된다. 실무에서는 메서드 오버라이딩, 메서드 재정의 둘다 사용한다.

super - 부모 참조

부모와 자식의 필드명이 같거나 메서드가 오버라이딩 되어 있으면, 자식에서 부모의 필드나 메서드를 호출할 수 없다. 이때 super 키워드를 사용하면 부모를 참조할 수 있다. super는 이름 그대로 부모 클래스에 대한 참조를 나타낸다.

extends9.png

super - 생성자

상속 관계의 인스턴스를 생성하면 결국 메모리 내부에는 자식과 부모 클래스가 각각 다 만들어진다. Child를 만들면 부모인 Parent까지 함께 만들어지는 것이다. 따라서 각각의 생성자도 모두 호출되어야 한다.

상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야 한다.
상속 관계에서 부모의 생성자를 호출할 때는 super(...) 를 사용하면 된다.

정리

  • 상속 관계의 생성자 호출은 결과적으로 부모에서 자식 순서로 실행된다. 따라서 부모의 데이터를 먼저 초기화하고 그 다음에 자식의 데이터를 초기화한다.
  • 상속 관계에서 자식 클래스의 생성자 첫줄에 반드시 super(...) 를 호출해야 한다. 단 기본 생성자(super())인 경우 생략할 수 있다