상속(inheritance)은 객체 지향 프로그램의 핵심이라고 할 수 있습니다. 라이브러리와도 다른 개념이고 이해하기도 힘듭니다. 아니 이해한 줄 알았는데 알고보니 모르겠더라가 더 많은 것 같습니다.
현실 세계의 상속은 부모나 혹은 친족 관계가 있는 사람 사이에서 한분이 돌아가시면 받는 재산의 권리를 말합니다. 쉬운말로 뒤를 잇는 일입니다. 뒤를 이어가는 것은 재산 뿐아니라 가업을 이어가는 것도 회사를 물려받는 것도 있겠고, 큰 범위에서는 정신을 계승한다는 말도 됩니다.
일단 상속이 어려운 말이라는 것은 알겠으니까 자바의 상속은 무엇인지 봐야겠습니다.
자바의 상속은 상위 클래스로부터 하위 클래스로 상속한다는 것입니다. 메소드와 멤버 변수를 상속합니다. 상위 클래스를 부모 클래스라고도 부르고 하위 클래스를 자식 클래스, 자손 클래스 등으로 부를 수 있습니다. 영어로 부모는 parent class 자식은 child class 입니다.
상속받은 자손클래스는 부모 클래스의 멤버변수와 메소드를 사용할 수 있습니다. 즉 코드를 작성하지 않아도 가지고 있는 것이죠. 7장에서 자바 AWT의 Frame 클래스를 상속받으면 윈도우창을 띄우는 프로그램을 30초만에 만들 수있습니다. 그런 것 처럼 현실에도 금수저가 있죠. 부모 잘 만나면 인생의 경험이 달라집니다.
현대의 프로그래머들은 독자적인 기술도 개발하지만 남들이 만들어놓은 코드(기업이 만든 프레임웤이나 오픈소스)들을 활용해서 퍼포먼스를 내고 있습니다. 프로그래밍의 역사가 오래되었으므로 많은 경우 이제는 노인이 되신 프로그래머들이 인생을 바쳐 개발한 코드겠죠. 지금 코드를 작성하는 개발자들은 유산들을 상속받은 것 입니다. 일단 내가 만들지 않았는데 내것 처럼 쓰니까. 자바의 자손 클래스도 그렇습니다.
그렇다면 남들이 만들어 놓은 것이 얼마나 있을까요?
자바의 스프링, MS의 .NET , JS node 같은 프레임워크, 파이썬의 수많은 모듈과 라이브러리 등이 있습니다. 지금은 공학을 전공한 사람이 어느정도의 프로그래밍 지식만 쌓아도 기본적인 인공지능 개발이나 주식거래 프로그램 등을 구현할 수 있을 정도입니다. 자신이 프로그래밍의 완벽한 전문가가 될 필요가 없어요. 툴이 상당수 개발 되어 있으니까.
라이브러리나 프레임워크를 이야기하니까 다소 동떨어진 이야기가 되버렸네요. 어쨋든 남들이 만들어 놓은 코드들을 이어 나간다는 개념은 비슷하다고 생각합니다.
다시 자바의 클래스로 돌아와서 클래스를 한번 만들어 보겠습니다.
*자바의 객체에 대하여는 포스트를 참고하세요.
이번에는 게임의 캐릭터를 클래스로 만들어 보겠습니다.
게임에는 다양한 직업이 있습니다. 그 직업에서 전직을 하면 더 강해진다거나 하는 설정이 있죠.
흔히 중세풍 판타지 RPG게임이 많이 팔리니까 캐릭터의 세계관이란게 거의 닮았지만,
세부적인 것은 설정하기 나름입니다. 그 관계를 클래스의 상속관계로 만들어 보는거지요.
우선 기본 캐릭터를 만들어 봅니다. 가장 기본이 되는 직업이고, 별다른 특기가 없이 전캐릭터 공통의 특성만 가지고 있습니다. 기본 캐릭터 클래스의 이름을 초보자 라고 합니다. 멤버변수는 이름과 클래스명칭, 체력(HP) 공격력(AP) 방어력(DP)라고 합니다. 메서드는 공격하기 방어하기 두개입니다.
class 초보자 {
String name;
String className;
int HP;
int AP;
int DP;
public void 공격하기() {
System.out.println("공격");
}
public void 방어하기() {
System.out.println("방어");
}
}
그 다음에 직업을 세분화해서 상속하겠습니다. 전사와 마법사,궁수 등이 떠오르네요. 전사를 선택하겠습니다.
class 전사 extends 초보자 {
}
extends 키워드로 부모클래스를 상속합니다. 이제 전사클래스의 인스턴스를 만들어 봅니다.
class 전사 extends 초보자 { } 에는 초보자를 extends한다는 내용뿐 아무 것도 없습니다. 그런데 이미 공격하기 메서드와 멤버변수인 AP(공격력)을 사용할 수 있습니다. extends 키워드로 이미 부모 클래스의 모든 능력들을 가지게 된 것입니다.
그런데 아무래도 전사는 초보자 보다 윗 등급의 직업입니다(직업의 CLASS를 말함) 뭔가를 더 추가 시켜야 됩니다. 예를들어 초보자는 못하지만 전사는 필살기를 사용할 수 있다고 합니다. 필살기 포인트를 멤버 변수로 추가합니다. 그리고 필살기 메서드를 추가합니다.
class 전사 extends 초보자 {
int SP; //Special Point 필살기에 사용됨
public void 필살기(){
System.out.println("전사 필살기");
}
}
*위 코드처럼 보이지만 실제로는 이렇게 더 많은 내용이 있습니다. 상속받은 초보자 클래스의 멤버변수와 메서드가 전사 클래스에 추가 된 것이죠.
class 전사 extends 초보자 {
String name;
String className;
int HP;
int AP;
int DP;
int SP; // Special Point
public void 공격하기() {
System.out.println("공격");
}
public void 방어하기() {
System.out.println("방어");
}
public void 필살기(){
System.out.println("전사 필살기");
}
}
상속과 관련된 protected 접근제어자는 언제 쓰는 것일까요? 예를 들어서 부모의 HP 를 private 으로 가려놓습니다.
class 초보자 {
String name;
String className;
private int HP; // private
int AP;
int DP;
public void 공격하기() {
System.out.println("공격");
}
public void 방어하기() {
System.out.println("방어");
}
}
만약 아래처럼 초보자에서 상속받은 HP를 전사 클래스에서 그대로 사용하려고 한다면
class 전사 extends 초보자 {
int SP; //Special Point 필살기에 사용됨
public void 필살기(){
System.out.println("전사 필살기");
}
public void showHP(){
System.out.println("HP : " + HP);
}
}
다음과 같은 에러메시지를 보게 됩니다.
The field 초보자.HP is not visible -> 안보입니다. HP가
?? 초보자를 상속받은 전사는 멤버변수를 자기 것 처럼 쓸 수 있던게 아니었나요? private 은 아닙니다.
여기의 해결책은 위에 나와있습니다. 11 quick fixes available: 여기 이클립스의 메시지를 잘 보시면 재미있습니다. 해결책을 추천해주는데 첫번째가 초보자 클래스의 HP를 protected로 바꿔서 visible (보이게) 로 바꾸는 것입니다. 그 다음은 constant 즉 static final 로 전사클래스의 static 상수를 만드는 것입니다. 부모의 멤버 변수에 접근하려는게 목적이니까 이건 해결책이 아니겠네요. 그 다음은 getter와 setter 를 사용하는 것입니다.
getter 와 setter에 대하여는 아래 포스팅 참고.
getter와 setter를 사용하면 접근은 할 수 있습니다. 그런데 부모로 부터 상속을 받았는데 뭔가 외부 클래스 같고 불편하죠. 그럴때 protected가 필요합니다. protected를 하면 상송된 하위 클래스를 제외한 나머지 외부 클래스에서는 private 처럼 보이지 않습니다. (같은 패키지 내에서는 사용가능함 ,외부클래스에서 안보임)
class 초보자 {
String name;
String className;
protected int HP; // private ->protected
int AP;
int DP;
public void 공격하기() {
System.out.println("공격");
}
public void 방어하기() {
System.out.println("방어");
}
}
마치 부모 자식간에도 상도덕이 존재하는 것 같은 기분이 듭니다. 부모가 자식에게 다 주려고 하지만 어떤 이유에서 인지 자식도 못만질 것이 있다는 것인가 봅니다. 물론 getter와 setter로 부모의 private에 접근할 수 있지만 부모클래스에 구현해야 하고 그렇게 하면 외부클래스에서 접근하는 것과 차이가 없습니다. 남남인 것 같네요.
만일 외부클래스에서 부모클래스에 접근해야 한다면 protected로 걸어놓고 getter와 setter를 추가합니다. 자손은 protected로 멤버변수에 직접 접근이 가능하고 외부클래스는 getter 와 setter 로 접근할 수 있으니 꽤 공평한 것 같네요. 자손이니까 그정도의 혜택은 있는겁니다.
그리고 private을 사용할 수 있도록 놔둔 것을 보니 쓸일이 있기는 한가 봅니다. 살다보면 예외란 것도 있으니까요.