JAVA와 같은 객체 지향 언어에서는 이미 존재하는 클래스를 확장하여 새로운 클래스를 만들 수 있습니다.
자식 클래스(하위 클래스)가 부모 클래스(상위 클래스)를 선택하여 그 부모의 멤버 변수나 함수들을 물려받아 확장할 수 있습니다. 이를 객체 지향 언어의 특성 중에 하나인 상속(Inheritance)이라고 합니다.
이와 같은 상속을 사용하는 이유는 재사용성을 높일 수 있고 중복된 코드를 줄일 수 있습니다. 또한, 개발자가 대규모 프로젝트를 위해 개발하는데 유용하게 사용할 수 있는 기능입니다.
그러면 하위 클래스가 상위 클래스를 어떻게 상속받아 사용할까요?
바로 아래와 같이 일반적인 클래스 정의 형태에 지정어 extends
를 사용하여 상위 클래스 이름을 명시해줍니다.
class SubClassName extends SuperClassName {
...
}
하위 클래스는 상위 클래스의 모든 멤버 변수와 메서드들을 상속받게 됩니다. 또한, 새로운 멤버 변수와 메서드를 추가할 수 있으며, 상위 클래스에 정의된 메서드를 재정의할 수 있습니다.
주의할 점은 extends
키워드 뒤에는 단 하나의 클래스만 올 수 있습니다. 즉, 단일 상속만을 지원합니다.
그럼 이 상속을 하는 경우는 어떤 경우가 있을까요?
일단, 상위 클래스는 상속받을 하위 클래스보다 더 추상적이고 일반적인 개념과 기능을 가집니다. 하위 클래스는 상위 클래스보다 더 구체적인 개념과 기능을 가질 때, 상속받습니다.
즉, 상위 클래스와 하위 클래스는 서로 성질이 같지만 존재하는 클래스보다 내가 새로 만들 클래스는 좀 더 기능이 많고 구체적이였으면 좋겠다면 상속을 받습니다.
괜찮은 클래스의 좋은 기능이 있다고 해서 상속받지는 않습니다.
중요한 점은 하위 클래스를 생성하게 되면 상위 클래스가 먼저 생성하게 됩니다.
그래서 만약에new SubClassName()
를 호출하면SuperClassName()
이 먼저 호출되게 됩니다.
즉, 상속 받은 하위 클래스의 생성자에서는 반드시 상위 클래스의 생성자를 호출합니다.
class SuperClass {
SuperClass() {
System.out.println("SuperClass Constructor...");
}
}
class SubClass extends SuperClass {
SubClass() {
System.out.println("SubClass Constructor...");
}
}
public class ConstructorTest {
public static void main(String[] args) {
SubClass subClass = new SubClass();
System.out.println("main...");
}
}
결과
SuperClass Constructor...
SubClass Constructor...
main...
위와 같은 코드를 보면 아까 설명했던 것처럼 상위 클래스의 생성자를 호출 후 하위 클래스의 생성자를 호출하는 것을 확인할 수 있습니다.
여기서 하위 클래스의 생성자에서 상위 클래스의 생성자를 super()
를 사용하여 명시적으로 호출할 수 있습니다. 또한, 매개변수를 갖는 super()
는 상위 클래스의 중복된 생성자를 구별하여 호출하는 역할도 합니다.
바로 아래의 코드로 확인해 보겠습니다.
class SuperClass {
int a, b;
SuperClass() {
a = 1;
b = 1;
}
SuperClass(int a, int b) {
this.a = a;
this.b = b;
}
}
class SubClass extends SuperClass {
int c;
SubClass() {
c = 1;
}
SubClass(int a, int b, int c) {
super(a, b);
this.c = c;
}
}
public class ConstructorTest {
public static void main(String[] args) {
SubClass subClass1 = new SubClass();
SubClass subClass2 = new SubClass(1, 2, 3);
System.out.println("a = "+subclass1.a + " b = "+subClass1.b +" c = "+subClass1.c);
System.out.println("a = "+subclass2.a + " b = "+subClass2.b +" c = "+subClass2.c);
}
}
결과
a = 1 b = 1 c = 1
a = 1 b = 2 c = 3
객체 subClass1에 대한 생성자에서는 컴파일러에 의해 상위 클래스의 생성자가 자동으로 호출되어 모든 멤버 변수들을 1로 배정하였고, subClass2 객체에 대한 생성자에서는 명시적으로 super(a, b)
를 호출하여 각 멤버 변수들을 지정된 값으로 초기화하였습니다.
형변환이라고 부르지만 어떻게 보면 객체간의 대입이라고도 부를 수 있다고 생각합니다.
형변환은 아래와 같이 상위 클래스로 변수를 선언하고 하위 클래스의 생성자로 인스턴스를 생성하는 것을 말합니다.
SuperClass obj = new SubClass();
하위 클래스는 상위 클래스에 대한 부분을 다 내포하고 있기 때문에 하위 클래스가 상위 클래스로 대입이 된다고 볼 수 있습니다.
여기에 만약, 하위 클래스 객체를 new
연산자를 통해 생성하고 메서드를 호출하는데, 그 메서드의 매개변수 타입이 상위 클래스의 타입인 경우 하위 클래스 객체가 대입될 수 있습니다. 왜냐하면 하위 클래스지만 상위 클래스로 형변환(업캐스팅)이 될 수 있기 때문입니다.
추가적으로 하위 클래스는 상위 클래스의 타입을 내포하고 있기 때문에 상위 클래스로의 묵시적 형 변환이 가능합니다.
하지만 중요한 점은 상위 클래스 객체를 하위 클래스 객체로 형변환하는 것은 불가능합니다.
또한 여기서 중요한 점은 만약 SuperClass obj = new SubClass();
를 통해 obj 객체를 생성했다는 가정하에, SubClass()
생성자에 의해 SubClass 클래스의 모든 멤버 변수에 대한 메모리는 생성되었지만 변수의 타입이 SuperClass 이므로 실제 접근 가능한 변수나 메서드는 SuperClass의 변수와 메서드입니다.
지금까지 JAVA의 특성 중 하나인 상속에 대해서 간단히 알아봤습니다.