상속은 객체지향 프로그래밍의 꽃이라고 할 수 있다. 사실 바로 이전 글의 팩토리 생성자에서 상속을 사용했다. 객체지향 프로그래밍에서 상속은 클래스의 멤버를 물려주는 것을 의미한다. 이때 물려주는 쪽을 부모 클래스(혹은 Super class)라고 하고 상속을 받는 쪽을 자식 클래스(혹은 Sub class)라고 한다.
기본 형태는 다음과 같다.
class 부모 클래스명 {
멤버 변수;
멤버 함수() {
}
}
class 자식 클래스명 extends 부모 클래스명 {
@override
멤버 함수() {
}
}
상속받는 쪽(자식 클래스)은 extends 키워드를 통해서 상속받고자 하는 부모 클래스를 지정한다. @override 어노테이션은 부모 클래스의 메서드를 재정의하고 싶을 때 사용한다. 재정의한다는 의미는 기존 메서드에서 구현한 내용 대신 다른 동작을 하는 코드를 구현하는 것이다.
다음 예제를 차근차근 살펴보자.
Student 클래스는 Person 클래스를 상속받고 있다. 따라서 Student 클래스는 자식 클래스이고 Person 클래스는 부모 클래스이다.
Person 클래스는 1개의 멤버 변수와 3개의 메서드로 구성되어 있다. 자식 클래스인 Student 클래스는 이 멤버 변수와 메서드를 모두 상속받는다. 따라서 해당 멤버 변수와 메서드를 별도로 Student 클래스 내에 선언하지 않아도 사용 가능하다. 즉 선언된 위치만 부모 클래스에 있을 뿐이지 자식 클래스 내에 존재하는 것과 같다.
코드를 좀 더 이해하기 쉽게 하려고 어떻게든 그림으로 그려보려고 한 흔적
만약 함수의 기능을 변경하고 싶다면 재정의하면 된다. 부모 클래스의 showInfo() 메서드를 재정의해보자. 예제 Line 20을 보면 @override 어노테이션이 있고 이것은 showInfo() 메서드를 재정의하기 위한 것이다. showInfo()는 부모 클래스에서는 name만 출력하고 있다. 자식 클래스에서는 자식 클래스의 멤버 변수인 studentID도 추가해서 출력하도록 변경했다.
이때 자식 클래스의 showInfo() 메서드 내부를 주목하자. Line 22를 보면 부모 클래스의 메서드인 getName() 메서드에 접근하기 위해서 super라는 키워드를 사용했다. super는 부모 클래스를 가리킨다. 하지만 생략도 가능하다. 자식 클래스는 이미 부모 클래스의 getName() 메서드도 상속받았기 때문이다.
또한 getName()을 통해서 name에 접근하지 않고 곧바로 name 변수를 출력해도 된다. name 역시 상속받았기 때문이다.
마지막으로 살펴볼 것은 Line 5의 this 키워드이다. this는 해당 인스턴스를 가리킨다. 여기서는 this.name이 Person 클래스의 인스턴스 변수인 name을 가리키고 name은 setName(String name) 메서드의 인자를 가리킨다. 변수명이 동일하기 때문에 구분하기 위해서 this로 인스턴스 변수를 명확히 가리킨 것이다.
상속은 왜 사용할까? 일단 코드를 재사용하기 때문에 클래스가 간소화되고수정 및 추가가 쉬워진다. 위 예제를 기준으로 보면 Student 클래스는 부모 클래스에 있는 멤버들을 일일이 다시 선언할 필요가 없었다. 따라서 Student 클래스는 매우 간결해졌다. 그리고 해당 클래스에서 필요한 기능은 쉽게 추가 가능하다. studentID 변수를 추가했고, showInfo() 메서드도 필요에 맞게 수정했다.
만약 Student 클래스 하나가 아닌 Employee 클래스, Employer 클래스, Manager 클래스, Programmer 클래스 등 사람과 관련된 수많은 객체를 위한 클래스를 생성한다고 생각해보라. 그러면 더욱 코드 재사용성이 값진 의미를 가진다. 또한 Employee 클래스 하위에 Manager 클래스, Programmer 클래스를 두는 방식으로 또 한 번 코드를 재사용할 수 있고 각 객체의 특징에 대해 유연하게 정의하고 수정 및 추가가 가능하다.