IT 개발노트
상속이란? 본문
1. 상속
1.1 상속의 정의와 장점
: 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것을 상속이라 한다.
쉽게 말하면, 부모 클래스의 멤버를 자식 클래스에게 물려주는 것이다.
상속은 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 코드의 중복을 줄여준다.
보다 적은 양의 코드로 새로운 클래스를 작성할 수 있고, 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 매우 용이하다.
다양한 객체(타입)을 상속을 통해서 하나의 객체(타입)으로 묶을 수 있다.
1.2 상속을 구현하는 방법
: 새로 작성하고자 하는 클래스의 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 'extesds'와 함께 써 주기만 하면 된다.
예를 들어, 새로 작성하려는 클래스의 이름이 Child이고, 상속받고자 하는 기존 클래스의 이름이 Parent라면 아래와 같이 작성하면 된다.
예시1
1 2 3 4 5 6 7 8 9 | class Parent { int field; void method() { ... } } class Child extends Parent { String field2; void method2() { ... } } |
위 두 클래스는 서로 상속 관계에 있다고 하며, 상속해주는 클래스를 '조상(부모, 상위, 기반)클래스'라 하고 상속 받는 클래스를 '자손(자식, 하위, 파생)클래스'라 한다.
* 자바는 다중 상속을 지원하지 않는다.
예시 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Parent { int field; void method() { ... } } class Child extends Parent { String field2; void method2() { ... } } class Child2 extends Parent, Child { int field3; void method3() { ... } } |
- 여러개의 부모 클래스를 상속할 수 없다. 그러므로 extends 뒤에는 단 하나의 부모 클래스만 와야 한다.
예시 3
1 2 3 4 5 6 7 8 9 | class Parent { int age; } class Child extends Parent { void play() { System.out.println("상속~"); } } |
부모에게 age라는 멤버 필드를 상속 받았지만, 자신이 가지고 있는 play() 메소드는 부모 클래스에게 아무런 영향도 미치지 못한다.
부모 클래스가 변경되면 자식 클래스는 자동적으로 영향을 받게 되지만, 자식 클래스가 변경되는 것은 부모 클래스에 아무런 영향을 주지 못한다.
자식 클래스는 부모 클래스의 모든 멤버를 상속 받으므로 항상 조상 클래스보다 같거나 많은 멤버를 갖는다.
상속에 상속을 거듭할수록 상속받는 클래스의 멤버 개수는 점점 늘어나게 된다.
생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
자식 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
접근 제어자로 private 또는 default가 사용된 멤버들은 상속되지 않는다기 보다는 상속은 받지만 자식 클래스로부터 접근이 제한되는 것이다.
예시4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Parent { int field; void method() { ... } } class Child extends Parent { String field2; void method2() { ... } } class Child2 extends Parent { int field3; void method3() { ... } } |
Child와 Child2 클래스에 공통으로 추가해야할 내용이 생긴다면 Parent 클래스에 추가하면 된다.
상속 관계이기 때문에 Parent 클래스의 기능을 사용할 수 있기 때문에 공통 기능 관리가 편해진다.
이처럼 같은 내용의 코드를 하나 이상의 클래스에 중복적으로 추가해야하는 경우에는 상속 관계를 잘 이용해서 코드의 중복을 최소화할 수 있다.
예시 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Parent { int field; void method() { ... } } class Child extends Parent { String field2; void method2() { ... } } class Child2 extends Parent { int field3; void method3() { ... } } class GrandChild extends Child { int field4; void method4() { ... } } |
자손클래스는 조상 클래스의 모든 멤버를 물려받으므로 GrandChild클래스는 Child클래스의 모든 멤버, Child클래스의 조상인 Parent클래스의 모든 멤버까지 상속받게 된다.
그래서 GrandChild클래스는 Child클래스의 자손이면서 Parent클래스의 자손이기도 하다.
좀 더 자세히 말하자면, Child클래스는 GrandChild클래스의 직접 조상이고, Parent크래스는 GrandChild클래스의 간접 조상이 된다.
그래서 GrandChild클래스는 Parent클래스와 간접적인 상속관계에 있다고 할 수 있다.
* 전체 프로그램을 구성하는 클래스들을 면밀히 설계 분석하여, 클래스 간의 상속 관계를 적절히 맺어 주는 것이 객체지향 프로그래밍에서 가장 중요한 부분이다.
예제 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package sungju.Java.InheritanceEx; class Tv { boolean power; // 전원상태(on/off) int channel; // 채널 void power() { power = !power; } void channelUp() { ++channel; } void channelDown() { --channel; } } class CaptionTv extends Tv { boolean caption; // 캡션상태(on/off) void displayCaption(String text) { if(caption) { // 캡션 상태가 on(true)일때만 text를 보여준다. System.out.println(text); } } } public class InheritanceEx { public static void main(String[] args) { CaptionTv ctv = new CaptionTv(); ctv.channel = 10; // 조상 클래스로부터 상속받은 멤버 ctv.channelUp(); // 조상 클래스로부터 상속받은 멤버 System.out.println(ctv.channel); ctv.displayCaption("Hello, World"); ctv.caption = true; // 캡션(자막) 기능을 켠다. ctv.displayCaption("Hello, World"); } } |
* 자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.
1.3 클래스간의 관계 - 포함관계
상속이외에도 클래스를 재사용하는 또 다른 방법이 있는데, 그것은 클래스 간에 '포함(Composite)'관계를 맺어주는 것이다.
클래스 간의 포함관계를 맺어 주는 것은 한 클래스의 멤버변수로 다른 클래스를 선언하는 것을 뜻한다.
예시 6
1 2 3 4 5 6 7 8 9 | class Point { int x; int y; } class Circle { Point c = new Point(); int r; } |
한 클래스를 작성하는데 다른 클래스를 멤버 변수로 정의하여 포함시키는 것은 좋은 생각이라 할 수 있다.
하나의 거대한 클래스를 작성하는 것보다 단위별로 여러 개의 클래스를 작성한 다음, 이 단위 클래스들을 포함 관계로 재사용하면 보다 간결하고 손쉽게 클래스를 작성할 수 있다.
또한 작성된 단위 클래스들은 다른 클래스를 작성하는데 재사용될 수 있을 것이다.
1.4 클래스 간의 관계 설정하기
클래스를 작성하는데 있어서 상속 관계를 맺어 줄 것인지 포함 관계를 맺어 줄 것인지 결정하는 것은 혼돈스러울 수 있다.
이럴 땐, "~은 ~이다.(is-a)" 와 "~은 ~을 가지고 있다.(has-a)"문장에 대입해보면 클래스 간의 관계가 보다 명확히 보일 것이다.
클래스를 가지고 문장을 만들었을 때 "~은 ~이다."라는 문장이 성립한다면, 서로 상속관계를 맺어주고, "~은 ~을 가지고 있다."는 문장이 성립한다면 포함관계를 맺어 주면 된다.
- 상속관계 : "~은 ~이다.(is-a)"
- 포함관계 : "~은 ~을 가지고 있다.(has-a)"
1.5 부모 생성자 호출(super 키워드)
: 자식 객체를 생성하면, 부모 객체가 먼저 생성되고 자식 객체가 그 다음에 생성된다.
모든 객체는 클래스의 생성자를 호출해야만 생성된다.
부모 생성자 또한, 예외는 아니다. 부모 생성자는 자식 생성자의 맨 첫줄에서 호출된다.
명시적으로 선언되지 않았다면, 컴파일러는 아래와 같은 코드를 추가시킨다.
예시 7
1 2 3 4 5 6 7 8 9 | class Parent { // 내용 } public class Child extends Parent { public Child() { super(); } } |
첫 줄에 super();가 추가된 것을 볼 수 있다. super()는 부모의 기본 생성자를 호출한다.
직접 정의된 생성자를 호출하고 싶으면 그에 맞는 매개변수를 전달하며 호출하면 된다.
super(매개변수1, 매개변수2, ...);
* 부모 생성자 호출은 무조건 자식 생성자 첫 줄에 위치해야 한다.
예제 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package sungju.Java.InheritanceEx; class People { public String name; public String ssn; public People(String name, String ssn) { this.name = name; this.ssn = ssn; } } class Student extends People { public int studentNo; public Student(String name, String ssn, int studentNo) { super(name, ssn); this.studentNo = studentNo; } } public class InheritanceEx { public static void main(String[] args) { Student student = new Student("홍길동", "123456-1234567", 1); System.out.println("name : " + student.name); System.out.println("ssn : " + student.ssn); System.out.println("studentNo : " + student.studentNo); } } |
1.6 단일 상속
: 자바에서의 상속은 단일 상속만을 허용한다. 그래서 하나 이상의 클래스로부터 상속을 받을 수 없다.
다중 상속을 허용하면 여러 클래스로부터 상속 받을 수 있기 때문에 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있다는 장점이 있다.
하지만, 클래스 간의 관계가 매우 복잡해진다는 것과 서로 다른 클래스로부터 상속 받은 멤버 간의 이름이 같은 경우 구별할 수 있는 방법이 없다는 단점을 가지고 있다.
단일 상속이 하나의 조상 클래스만을 가질 수 있기 때문에 다중 상속에 비해 불편한 점도 있겠지만, 클래스 간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있게 만들어 준다는 점에서 다중 상속보다 유리하다.
상속 관계와 포함 관계를 적절히 잘 활용하면 다중 상속을 한 것과 같은 형태의 구현도 가능하다.
1.7 Object클래스 - 모든 클래스의 조상
: Object클래스는 모든 클래스 상속 계층도의 제일 위에 위치하는 조상 클래스이다.
다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 Object클래스로부터 상속받게 함으로써 이것을 가능하게 한다.
1 2 3 | class ClassName extends Object { ... } |
자바는 아무 것도 상속받지 않은 최상위 클래스를 컴파일 시에 위 코드처럼 자동적으로 'extends Object'를 추가하여 상속받고록 한다.
이렇게 함으로써 Object클래스가 모든 클래스의 조상이 되도록 한다.
다른 클래스를 상속받는다 하더라도 찾아 올라가다 보면 결국 마지막 최상위 조상은 Object 클래스일 것이다.
* toString(), equals(Object o)와 같은 메서드를 따로 정의하지 않고도 사용할 수 있었던 이유가 바로 이 메서드들이 Object 클래스에 정의된 것들이기 때문이다.