상속은 객체 지향 프로그래밍에서 아주 중요한 요소 중 하나이다.
기존 클래스의 필드와 메서드를 새로운 클래스에서 재상용하게 해준다면 얼마나 편리할까? 바로 이것이 상속이다. 상속을 하려면 extends 키워드를 사용하면 된다.
extends 키워드는 대상을 하나만 선택할 수 있다.
용어 정리
상속 관계를 알아보기 위해 자동차를 예를 들어 설명하겠다.
자동차에는 여러가지 종류의 자동차를 볼 수 있다. 전기자동차, 가솔린 자동차 등 추상적인 개념으로 보자면 이들은 모두 "자동차"이고 구체적으로 보면 전기, 가솔린로 볼 수 있다. 이들의 공통점으로는 움직이거나, 브레이크는 공통점으로 묶을 수 있고, 충전방식을 다르게 표현할 수 있다. 이제 이걸 상속으로 표현해 보자면, 움직이는 것을 상속받아서 충전하는 부분을 확장해 나가면 되는 것이다.
코드를 보면서 파악해 보도록 하자.
public class Car {
public void move() {
System.out.println("자동차를 움직입니다.")
}
}
위 코드에서 Cas 클래스는 부모 클래스가 되고, 공통적인 기능은 움직이는 기능 move()가 포함되어 있는 것을 확인할 수 있다.
public class ElectricCar extedns Car() {
public void charge() {
System.out.println("자동차를 충전합니다.")
}
}
위 코드 전기차에서는 extends 키워드를 사용하여 Car 클래스를 상속받고, 덕분에 ElectricCar에서도 move()를 사용할 수 있게 되었다.
public class GasCar extends Car() {
public void fillup() {
System.out.println("자동차를 충전합니다.")
}
}
위 코드에서도 마찬가지로 extends Car를 사용해서 부모 클래스인 Car를 상속 받았고, 여기서도 move()를 사용할수 있게 되었다.
상속 구조도
위 사진에서 화살표 방향을 주의해서 살펴보자.
화살표 방향이 이렇게 생긴 이유는 자식 클래스는 부모 클래스로부터 기능을 물려 받아서 접근이 가능하지만, 부모 클래스 입장에서는 누가 상속을 받는지도 모르고 부모 클래스에서 자식 클래스로 접근을 할 수 없기 때문에 방향이 자식 클래스에서 가리키게 된 것이다.
참고로 자바는 다중 상속이 불가하다!!
만약 다중 상속이 된다고 생각하면 어떤 일이 벌어지게 될까?
부모 클래스를 2개 상속받는다고 생각해보자. 만약 부모 클래스의 move()기능이 둘 다 있다면 과연 자식 클래스 입장에서는 어떤 부모의 move()를 사용해야 할지 애매한 문제가 발생하게 된다. 이것을 다이아몬드 문제라고 한다. 다중 상속을 하게 되면 이와 같은 애매한 문제와 클래스 계층 구조가 매우 복잡해 질 수 있다. 이런 문제점들 때문에 자바에서는 클래스의 다중 상속을 허용하지 않는다.
상속 부분에 있어서 제일 중요하다고 생각하는 부분이 바로 메모리 구조를 파악하는 것이라고 생각한다.
ElectricCar electriCar = new ElectriCar();
위 코드 부분에서 new ElectriCar()를 호출하면 ElectriCar뿐만 아니라 상속 관계에 있는 Car 까지 함께 포함해서 인스턴스를 생성한다. 참조값은 x001로 하나이지만 실제로 그 안에서는 ElectriCar하고 Car두 클래스의 정보가 함께 들어있다는 것이다.
상속이라고 해서 단순하게 부모의 필드와 메서드만을 물려받는 것이 아니라 클래스도 함께 포함해서 생성되는 것이다. 겉으로 보기에는 하나의 인스턴스를 생성하는 것 처럼 보일 수 있지만 실제로는 부모와 자식이 모두 생성되고 공간도 구분되어 있는 것이다.
electricCar.charge()
electricCar.charge()를 호출하면 참조값인 x001을 확인해서 x001.charge()를 호출한다. 상속 관계의 경우에는 내부에 부모 클래스, 자식 클래스 모두가 존재하기 때문에 접근을 어디에 먼저 할 지를 선택해야 한다. 이때는 호출하는 변수의 타입을 기준으로 선택한다. elecricCar의 변수의 타입이 ElectricCar이기 때문에 인스턴스 내부에 같은 타입은 ElectricCar를 통해서 charge()를 호출한다.
electricCar.move()
그렇다면 electricCar.move()를 호출하면 어떻게 되는 걸까?
먼저 electricCar.move()룰 호출하게 된다면 참조값인 x001로 먼저 이동하게 된다.
호출하는 변수의 타입은 ElecticCar이기 때문에 타입에 맞게 선택하게 된다.
하지만 ElectricCar에는 move()라는 메서드가 없기 대문에 부모타입으로 올라가서 찾게 된다. 부모 타입인 Car로 올라가서 move()가 있는지 확인후 move()가 있기 때문에 부모에 있는 move()를 호출하게 된다.
만약 부모에서도 해당 기능을 찾지 못하게 된다면 더 상위 부모로 찾아가게 된다. 계속 올라갔는데도 찾지 못한다면 컴파일 오류를 발생하게 된다.
상속과 메모리에서 중요한 3가지를 요약해 보았다.