[Java의 정석]제7장 객체지향개념 2 - 1. 상속(Inheritance)

류명운

·

2014. 7. 3. 22:32

반응형

1. 상속(Inheritance)


1.1 상속의 정의와 장점

상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다. 상속을 통해서 클래스를 작성하면, 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있고 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 매우 용이하다.

이러한 특징은 코드의 재사용성을 높이고 코드의 중복을 제거하여 프로그램의 생산성과 유지보수에 크게 기여한다.
자바에서 상속을 구현하는 방법은 아주 간단하다. 새로 작성하고자 하는 클래스의 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 'extends'와 함께 써 주기만 하면 된다.

예를 들어 새로 작성하려는 클래스의 이름이 Child이고 상속받고자 하는 기존 클래스의 이름이 Parent라면 다음과 같이 하면 된다.


class Child extends Parent {
// ...
}


이 두 클래스는 서로 상속 관계에 있다고 하며, 상속해주는 클래스를 '조상클래스'라 하며 상속 받는 클래스를 '자손 클래스'라 한다.
[참고]서로 상속관계에 있는 두 클래스를 아래와 같은 용어를 사용해서 표현하기도 한다.

조상클래스 자손클래스
부모(parent) 클래스 자식(child) 클래스
상위(super) 클래스 하위(sub) 클래스
기반(base) 클래스 파생된(derived) 또는 확장된(extended) 클래스


다음과 같이 서로 상속관계에 있는 두 클래스를 그림으로 표현하면 다음과 같다.

class Parent { }
class Child extends Parent { }



클래스는 타원으로 표현했고 클래스간의 상속 관계는 화살표로 표시했다. 이와 같이 클래스간의 상속관계를 그림으로 표현한 것을 상속계층도(Class Hierarchy)라고 한다.
프로그램이 커질수록 클래스간의 관계가 복잡해지는데, 이 때 위와 같이 그림으로 표현하면, 클래스간의 관계를 보다 쉽게 이해할 수 있다.

자손 클래스는 조상클래스의 모든 멤버를 상속받기 때문에, Child클래스는 Parent클래스의 멤버들을 포함한다고 할 수 있다. 클래스는 멤버들의 집합이므로 클래스 Parent와 Child의 관계를 다음과 같이 표현할 수도 있다.



만일 Parent클래스에 age라는 정수형 변수를 멤버변수로 추가하면, 자손 클래스는 조상의 멤버를 상속받기 때문에, Child클래스는 자동적으로 age라는 멤버변수가 추가된 것과 같은 효과를 얻는다.

class Parent {
int age;
}
class Child extends Parent { }



클래스이름 클래스의 멤버
Parent age
Child age

이번엔 반대로 자손인 Child클래스에 새로운 멤버로 play() 메서드를 추가해보자.

class Parent {
int age;
}
class Child extends Parent {
void play() {
System.out.println("놀자~");
}
}



클래스이름 클래스의 멤버
Parent age
Child age, play()

Child클래스에 새로운 코드가 추가되어도 조상인 Parent클래스는 아무런 영향도 받지 않는다. 여기서 알 수 있는 것처럼, 조상클래스가 변경되면 자손클래스는 자동적으로 영향을 받게 되지만, 자손클래스가 변경되는 것은 조상클래스에 아무런 영향을 주지 못한다.

자손클래스는 조상클래스의 모든 멤버를 상속 받으므로 항상 조상클래스보다 같거나 많은 멤버를 갖는다. 즉, 상속에 상속을 거듭할 수록 상속받는 클래스의 멤버 개수는 점점 늘어나게 된다.
그래서 상속을 받는다는 것은 조상클래스를 확장(extend)한다는 의미로 해석할 수도 있으며 이 것이 상속에 사용되는 키워드가 'extends'인 이유이기도 하다.
[참고] 생성자나 초기화 블럭은 상속되지 않는다. 오직 멤버변수와 메서드만 상속된다.
[참고] 접근제어자(Access Modifier)로 private 또는 default가 사용된 멤버들은 상속되지 않는다기보다는 상속은 받지만 자손 클래스로부터의 접근이 제한되는 것으로 보는 것이 옳다.

이번엔 Parent클래스로부터 상속받는 Child2클래스를 새로 작성해보자. Child2클래스를 포함한 세 클래스간의 상속계층도는 다음과 같을 것이다.

class Parent { }
class Child extends Parent { }
class Child2 extends Parent { }



클래스 Child와 Child2가 모두 Parent클래스를 상속받고 있으므로 Parent클래스와 Child클래스, 그리고 Parent클래스와 Child2클래스는 서로 상속관계에 있지만 클래스 Child와 Child2간에는 서로 아무런 관계도 성립되지 않는다. 클래스간의 관계에서 형제 관계와 같은 것은 없다. 부모와 자식의 관계(상속관계)만이 존재할 뿐이다.

만일 Child클래스와 Child2클래스에 공통적으로 추가되어야 하는 멤버(멤버변수나 메서드)가 있다면, 이 두 클래스에 각각 따로 추가해주는 것보다는 이들의 공통조상인 Parent클래스에 추가하는 것이 좋다.
Parent클래스의 자손인 Child클래스와 Child2클래스는 조상의 멤버를 상속받기 때문에, Parent클래스에 새로운 멤버를 추가해주는 것은 Child클래스와 Child2클래스에 새로운 멤버를 추가해주는 것과 같은 효과를 얻는다.
이제는 Parent클래스 하나만 변경하면 되므로 작업이 간단해진다. 이보다 더 중요한 사실은 같은 내용의 코드를 한 곳에서 관리함으로써 코드의 중복이 줄어든 다는 것이다. 코드의 중복이 많아지면 유지보수가 어려워지고 일관성을 유지하기 어렵다.

이처럼 같은 내용의 코드를 하나 이상의 클래스에 중복적으로 추가해야하는 경우에는 상속관계를 이용해서 코드의 중복을 최소화해야한다. 프로그램이 어떤 때는 잘 동작하지만 어떤 때는 오동작을 하는 이유는 중복된 코드 중에서 바르게 변경되지 않은 곳이 있기 때문이다.

여기에 또다시 Child클래스로부터 상속받는 GrandChild라는 새로운 클래스를 추가한다면 상속계층도는 다음과 같을 것이다.

class Parent { }
class Child extends Parent { }
class Child2 extends Parent { }
class GrandChild extends Child { }



자손클래스는 조상클래스의 모든 멤버를 물려받으므로 GrandChild클래스는 Child클래스의 모든 멤버, Child클래스의 조상인 Parent클래스로부터 상속받은 멤버까지, 상속받게 된다.
그래서 GrandChild클래스는 Child클래스의 자손이면서 Parent클래스의 자손이기도 하다. 좀더 정확히 말하자면, Child클래스는 GrandChild클래스의 직접 조상이고, Parent클래스는 GrandChild클래스의 간접 조상이 된다. 그래서 GrandChild클래스는 Parent클래스와 간접적인 상속관계에 있다고 할 수 있다.

이제 Parent클래스에 전과 같이 정수형 변수인 age를 멤버변수로 추가해 보자.
class Parent {
int age;
}
class Child extends Parent { }
class Child2 extends Parent { }
class GrandChild extends Child { }



클래스이름 클래스의 멤버
Parent age
Child age
Child2 age
GrandChild age

Parent클래스는 클래스 Child, Child2, GrandChild의 조상이므로 Parent클래스에 추가된 멤버변수 age는 Parent클래스의 모든 자손에 추가된다. 반대로 Parent클래스에서 멤버변수 age를 제거 한다면, Parent의 자손클래스인 Child, Child2, GrandChild에서도 제거된다.
이처럼 조상클래스만 변경해도 모든 자손클래스에, 자손의 자손 클래스에까지 영향을 미치게 된다.

클래스간의 상속관계를 맺어 주면 자손클래스들의 공통적인 부분은 조상클래스에서 관리하고 자손 클래스는 자신에 정의된 멤버들만 관리하면 되므로 각 클래스의 코드가 적어져서 관리가 쉬워진다.
전체 프로그램을 구성하는 클래스들을 면밀히 설계 분석하여, 클래스간의 상속관계를 적절히 맺어 주는 것이 객체지향 프로그래밍에서 가장 중요한 부분이다.


[예제7-1] CaptionTvTest.java

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);
}
}
}

class CaptionTvTest {
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"); // 캡션을 화면에 보여 준다.
}
}
[실행결과]
11
Hello, World

Tv클래스로부터 상속받고 기능을 추가하여 CaptionTv클래스를 작성하였다. 멤버변수 caption은 캡션기능의 상태를 저장하기 위한 boolean형 변수이고, displayCaption(String text)는 매개변수로 넘겨받은 문자열 text를 캡션이 켜져 있는 경우(caption의 값이 true인 경우)에만 화면에 출력한다.
자손클래스의 인스턴스를 생성하면 조상클래스의 멤버도 함께 생성되기 때문에 따로 조상클래스의 인스턴스를 생성하지 않고도 조상클래스의 멤버들을 사용할 수 있다.




1.2 클래스간의 관계 - 포함관계

지금까지 상속을 통해 클래스간에 관계를 맺어 주고 클래스를 재사용하는 방법에 대해서 알아보았다. 상속이외에도 클래스를 재사용하는 또 다른 방법이 있는데, 그 것은 클래스간에 '포함(Composite)'관계를 맺어 주는 것이다. 클래스간의 포함관계를 맺어 주는 것은 한 클래스의 멤버변수로 선언하여 다른 클래스를 포함시키는 것을 뜻한다.
원(Circle)을 표현하기 위한 Circle이라는 클래스를 다음과 같이 작성하였다고 가정하자.


class Circle {
int x; // 원점의 x좌표
int y; // 원점의 y좌표
int r; // 반지름(radius)
}


그리고 좌표상의 한 점을 다루기 위해 Point클래스가 다음과 같이 작성되어 있다고 가정하자.


class Point {
int x; // x좌표
int y; // y좌표
}


Point클래스를 재사용해서 Circle클래스를 작성한다면 다음과 같이 할 수 있을 것이다.


class Circle {
Point c = new Point(); // 원점
int r;
}


이와 같이 한 클래스를 작성하는 데 다른 클래스를 멤버변수로 정의하여 포함시키는 것은 좋은 생각이다. 하나의 거대한 클래스를 작성하는 것보다 단위별로 여러 개의 클래스를 작성한 다음, 이 단위 클래스들을 포함관계로 재사용하면 보다 간결하고 손쉽게 클래스를 작성할 수 있을 것이다. 또한 작성된 단위 클래스들은 다른 클래스를 작성하는데 재사용될 수 있을 것이다.


class Car {
Engine e = new Engine(); // 엔진
Door[] d = new Door[4]; // 문, 문의 개수를 넷으로 가정하고 배열로 처리했다.
//...
}


위와 같은 Car클래스를 작성할 때, Car클래스의 단위구성요소인 Engine, Door와 같은 클래스를 미리 작성해 놓고 이 들을 Car클래스의 멤버변수로 선언하여 포함관계를 맺어 주면, 클래스를 작성하는 것도 쉽고 코드도 간결해서 이해하기도 쉽다. 그리고 단위클래스별로 코드를 작게 나누어서 작성되어 있기 때문에 코드를 관리하는데도 수월하다.



1.3 클래스간의 관계 결정하기

클래스를 작성하는데 있어서 상속관계를 맺어 줄 것인지 포함관계를 맺어 줄 것인지 결정하는 것은 때때로 혼동스러울 수 있다.
전에 예를 든 Circle클래스의 경우, Point클래스를 포함시키는 대신 상속관계를 맺어 주었다면 다음과 같을 것이다.


class Circle extends Point {
int r;
}


두 경우를 비교해 보면 Circle클래스를 작성하는데 있어서 Point클래스를 포함시키거나 상속받도록 하는 것은 결과적으로 별 차이가 없어 보인다.
그럴 때는 '~은 ~이다(is-a)'와 '~은 ~을 가지고 있다(has-a)'를 넣어서 문장을 만들어 보면 클래스들간의 관계가 보다 명확해 진다.


원(Circle)은 점(Point)이다. - Circle is a Point.
원(Circle)은 점(Point)를 가지고 있다. - Circle has a Point.


원은 원점(Point)와 반지름으로 구성되므로 위의 두 문장을 비교해 보면 첫 번째 문장보다 두 번째 문장이 더 옳다는 것을 알 수 있을 것이다.
이처럼 클래스를 가지고 문장을 만들었을 때 '~은 ~이다.'라는 문장이 성립한다면, 서로 상속관계를 맺어 주고, '~은 ~을 가지고 있다.'는 문장이 성립한다면 포함관계를 맺어 주면 된다. 그래서 Circle클래스와 Point클래스간의 관계는 상속관계 보다는 포함관계를 맺어 주는 것이 더 옳다.

몇 가지 더 예를 들면, Car클래스와 SportsCar클래스는 'SportsCar는 Car이다.'와 같이 문장을 만드는 것이 더 옳기 때문에 이 두 클래스는 Car클래스를 조상으로 하는 상속관계를 맺어 주어야 한다.
Card클래스와 Deck클래스는 'Deck는 Card를 가지고 있다.'와 같이 문장을 만드는 것이 더 옳기 때문에 Deck클래스에 Card클래스를 포함시켜야 한다.
[참고]Deck은 카드 한 벌을 뜻한다.


상속관계 - '~은 ~이다.(is-a)'
포함관계 - '~은 ~을 가지고 있다.(has-a)'


[참고]프로그램에 사용되는 모든 클래스들을 분석하여 가능한 많은 관계를 맺어 주도록 노력하여 코드의 재사용성을 높여야 한다.

[예제7-2] DrawShape.java

import java.awt.Frame;
import java.awt.Graphics;

class DrawShape extends Frame {
public static void main(String[] args)
{
DrawShape win = new DrawShape("도형그리기");
}

public void paint(Graphics g) {
Point[] p = { new Point(100, 100), new Point(140, 50), new Point(200, 100)};
Triangle t = new Triangle(p);
Circle c = new Circle(new Point(150, 150), 50);


// 원을 그린다.
g.drawOval(c.center.x, c.center.y, c.r, c.r);

// 직선 3개로 삼각형을 그린다.
g.drawLine(t.p[0].x, t.p[0].y, t.p[1].x, t.p[1].y);
g.drawLine(t.p[1].x, t.p[1].y, t.p[2].x, t.p[2].y);
g.drawLine(t.p[2].x, t.p[2].y, t.p[0].x, t.p[0].y);
}
DrawShape(String title) {
super(title);
setSize(300, 300);
setVisible(true);
}
}

class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
Point() {
this(0,0);
}
}

class Circle {
Point center; // 원의 원점좌표
int r; // 반지름
Circle() {
this(new Point(0, 0), 100);
}
Circle(Point center, int r) {
this.center = center;
this.r = r;
}
}

class Triangle {
Point[] p = new Point[3]; // 3개의 Point인스턴스를 담을 배열을 생성한다.
Triangle(Point[] p) {
this.p = p;
}
Triangle(Point p1, Point p2, Point p3) {
p[0] = p1;
p[1] = p2;
p[2] = p3;
}
}



Circle클래스와 Triangle클래스를 이용하여 원과 삼각형을 그려보았다. 자바의 AWT를 이용해서 새로운 윈도우를 만들고 여기에 도형을 그렸다. DrawShape클래스의 인스턴스 win을 생성하면 자동적으로 paint(Graphics g)메서드가 호출되어 화면에 도형을 그린다.
아직 AWT를 이용한 윈도프로그래밍을 배우지 않았으니, Circle클래스와 Triangle클래스를 작성하고 사용하는 부분만 이해해도 좋다.

[예제7-3] DeckTest.java

class DeckTest {
public static void main(String args[]) {
Deck d = new Deck(); // 카드 한 벌(Deck)을 만든다.
Card c = d.pick(0); // 섞기 전에 제일 위의 카드를 뽑는다.
System.out.println(c);
d.shuffle(); // 카드를 섞는다.
c = d.pick(0); // 섞은 후에 제일 위의 카드를 뽑는다.
System.out.println(c);
}
}

// Deck클래스
class Deck {
final int CARD_NUM = 52; // 카드의 개수
Card c[] = new Card[CARD_NUM];

Deck () { // Deck의 카드를 초기화한다.
int i=0;

for(int k=Card.KIND_MAX; k > 0; k--) {
for(int n=1; n < Card.NUM_MAX + 1 ; n++) {
c[i++] = new Card(k, n);
}
}
}

Card pick(int index) { // 지정된 위치(index)에 있는 카드 하나를 선택한다.
return c[index%CARD_NUM];
}

Card pick() { // Deck에서 카드 하나를 선택한다.
int index = (int)(Math.random() * CARD_NUM);
return pick(index);
}

void shuffle() { // 카드의 순서를 섞는다.
for(int n=0; n < 1000; n++) {
int i = (int)(Math.random() * CARD_NUM);
Card temp = c[0]; // 첫 번째 카드와 임의로 선택된 카드를 서로 바꾼다.
c[0] = c[i];
c[i] = temp;
}
}
}

// Card클래스
class Card {
static final int KIND_MAX = 4; // 카드 무늬의 수
static final int NUM_MAX = 13; // 무늬별 카드 수

static final int SPADE = 4;
static final int DIAMOND = 3;
static final int HEART = 2;
static final int CLOVER = 1;

int kind;
int number;

Card() {
this(SPADE, 1);
}

Card(int kind, int number) {
this.kind = kind;
this.number = number;
}

public String toString() {
String kind="";
String number="";

switch(this.kind) {
case 4 :
kind = "SPADE";
break;
case 3 :
kind = "DIAMOND";
break;
case 2 :
kind = "HEART";
break;
case 1 :
kind = "CLOVER";
break;
default :
}

switch(this.number) {
case 13 :
number = "K";
break;
case 12 :
number = "Q";
break;
case 11 :
number = "J";
break;
default :
number = this.number + "";
}
return "kind : " + kind + ", number : " + number;
}
}
[실행결과]
kind : SPADE, number : 1
kind : HEART, number : 7

Deck클래스를 작성하는데 Card클래스를 재사용하여 포함관계로 작성하였다. 카드 한 벌(Deck)는 모두 52장의 카드로 이루어져 있으므로 Card클래스를 크기가 52인 배열로 처리하였다.
shuffle()은 카드 한 벌의 첫 번째 장과 임의로 선택한 위치에 있는 카드의 위치를 서로 바꾸는 방식으로 카드를 섞는다. random()을 사용했기 때문에 매 실행 시 마다 결과가 다르게 나타날 것이다.



1.4 단일상속(Single Inheritance)

C++에서는 여러 클래스로부터 상속받는 다중상속(Multiple Inheritance)을 허용하지만, 자바에서는 단일 상속만을 허용하기 때문에 하나 이상의 클래스로부터 상속을 받을 수 없다.
예를 들면, TV클래스와 VCR클래스가 있을 때, 이 두 클래스로부터 상속을 받는 TVCR클래스를 작성할 수 없다.
그래서 TVCR클래스는 조상클래스로 TV클래스와 VCR클래스 중 하나만 선택해야한다.


class TVCR extends TV, VCR { // 이와 같은 표현을 허용하지 않는다.
//...
}


다중상속을 허용하면 여러 클래스로부터 상속받을 수 있기 때문에 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있다는 장점이 있지만, 클래스간의 관계가 매우 복잡해진다는 것과 서로 다른 클래스로부터 상속받은 멤버들간의 이름이 같은 경우 구별할 수 있는 방법이 없다는 단점을 가지고 있다.
만일 다중상속을 허용해서 TVCR클래스가 TV클래스와 VCR클래스를 모두 조상으로 하여 두 클래스의 멤버들을 상속받는다고 가정해 보자.
TV클래스에도 power()라는 메서드가 있고, VCR클래스에도 power()라는 메서드가 있을 때 자손인 TVCR클래스는 어느 조상클래스의 power()메서드를 상속받게 되는 것일까?
둘 다 상속받게 된다면, TVCR클래스 내에서 선언부(이름과 매개변수)만 같고 서로 다른 내용의 두 메서드를 어떻게 구별할 것인가?
static메서드라면, 메서드 이름 앞에 클래스의 이름을 붙여서 구별할 수 있다지만, 인스턴스메서드의 경우 선언부가 같은 두 메서드를 구별할 수 있는 방법은 없다.
이것을 해결하는 방법은 조상클래스의 메서드의 이름인 매개변수를 바꾸는 방법 밖에 없다. 이렇게 하면 그 조상클래스의 power()메서드를 사용하던 모든 클래스들도 변경을 해야 하므로 그리 간단한 문제가 아니다.

자바에서는 다중상속의 이러한 문제점을 해결하기 위해 다중상속의 장점을 포기하고 단일상속만을 허용한다. 대신 앞으로 배우게 될 인터페이스(interface)를 이용해서 보완된 형태의 다중상속을 구현할 수 있도록 하고 있다.
단일 상속이 하나의 조상클래스만을 가질 수 있기 때문에 다중상속에 비해 불편한 점도 있겠지만, 클래스간의 관계가 보다 명확해지고 코드를 더욱 신뢰성있게 만들어 준다는 점에서는 다중상속보다 유리하다.

[예제7-4] TVCR.java

class Tv {
boolean power; // 전원상태(on/off)
int channel; // 채널

void power() { power = !power; }
void channelUp() { ++channel; }
void channelDown() { --channel; }
}

class VCR {
boolean power; // 전원상태(on/off)
int counter = 0;
void power() { power = !power; }
void play() { /* 내용생략*/ }
void stop() { /* 내용생략*/ }
void rew() { /* 내용생략*/ }
void ff() { /* 내용생략*/ }
}

class TVCR extends Tv {
VCR vcr = new VCR();
int counter = vcr.counter;

void play() {
vcr.play();
}
void stop() {
vcr.stop();
}
void rew() {
vcr.rew();
}
void ff() {
vcr.ff();
}
}

자바는 다중상속을 허용하지 않으므로 Tv클래스를 조상으로 하고, VCR클래스는 TVCR클래스에 포함시켰다. 그리고 TVCR클래스에 VCR클래스의 메서드와 일치하는 선언부를 가진 메서드를 선언하고 내용은 VCR클래스의 것을 호출해서 사용하도록 했다. 외부적으로는 TVCR클래스의 인스턴스를 사용하는 것처럼 보이지만 내부적으로는 VCR클래스의 인스턴스를 생성해서 사용하는 것이다.
이렇게 함으로써 VCR클래스의 메서드의 내용이 변경되더라도 TVCR클래스의 메서드들 역시 변경된 내용이 적용되는 결과를 얻을 수 있을 것이다.




1.4 Object클래스 - 모든 클래스의 조상

Object클래스는 모든 클래스 상속계층도의 제일 위에 위치하는 조상클래스이다. 다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 Object클래스로부터 상속받게 함으로써 이 것을 가능하게 한다.
만일 다음과 같이 다른 클래스로부터 상속을 받지 않는 Tv클래스를 정의하였다고 하자.


class Tv {
// ...
}


위의 코드를 컴파일 하면 컴파일러는 위의 코드를 다음과 같이 자동적으로 'extends Object'를 추가하여 Tv클래스가 Object클래스로부터 상속받도록 한다.


class Tv extends Object {
// ...
}


이렇게 함으로써 Object클래스가 모든 클래스의 조상이 되도록 한다. 만일 다른 클래스로부터 상속을 받는다고 하더라도 상속계층도를 따라 조상클래스, 조상클래스의 조상클래스를 찾아 올라가다 보면 결국 마지막 최상위 조상은 Object클래스일 것이다.
[참고]이미 어떤 클래스로부터 상속받도록 작성된 클래스에 대해서는 컴파일러가 'extends Object'를 추가하지 않는다.


class Tv {
//...
}

class CaptionTv extends Tv {
// ...
}


위와 같이 Tv클래스가 있고, Tv클래스를 상속받는 CaptionTv가 있을 때 상속계층도는 다음과 같다.


[참고]상속계층도를 단순화하기 위해서 Object클래스를 생략하는 경우가 많다.

이처럼 모든 상속계층도의 최상위에는 Object클래스가 위치한다. 그래서 자바의 모든 클래스들은 Object클래스의 멤버들을 상속 받기 때문에 Object클래스에 정의된 멤버들을 사용할 수 있다.
그동안 toString()이나 equals(Object o)와 같은 메서드를 따로 정의하지 않고도 사용할 수 있었던 이유는 이 메서드들이 Object클래스에 정의된 것들이기 때문이다.
Object클래스에는 toString(), equals()와 같은 모든 인스턴스가 가져야 할 기본적인 8개의 메서드를 정의해 놓고 있으며 이에 대해서는 후에 자세히 학습하게 될 것이다. 


반응형