상속

양성빈·2022년 6월 17일

참고
자바의 정석
https://yadon079.github.io/2020/java/07-object-oriented-programming-ii-01
https://blog.daum.net/question0921/946

상속

상속의 정의와 장점

상속이라고 생각하면 '부모님이 자식한테 재산을 상속한다.'의 상속이 먼저 떠오를 것이다. 프로그래밍에서 상속도 이와 유사한 개념이다. 프로그래밍에서 상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다. 상속을 이용하면 적은양의 코드로 새로운 클래스를 작성할 수도 있고 공통적인 코드를 쉽게 관리 할 수 있기 때문에 코드의 추가 및 변경이 매우 용이하다. 또한 코드의 재사용성이 높이지면서 중복이 제거되고 생산성과 유지보수성에 크게 기여한다.
상속을 구현하는 법은 클래스 이름 뒤에 extends라는 키워드를 붙여주고 상속할 클래스 이름을 적어주면 된다.

class Child extends Parent {
}

위의 코드는 상속을 표현한것인데 Parent 클래스와 Child 클래스는 서로 상속관계라 하면 Parent클래스를 부모 클래스라고 하며 Child 클래스를 자식 클래스라고 한다.

부모 클래스 = 조상 클래스 = 상위 클래스 = 기반 클래스
자식 클래스 = 자손 클래스 = 하위 클래스 = 파생된 클래스

참고로 구체적인 클래스를 설계할 때 상속을 많이 이용한다. 즉, 새로운 클래스를 정의할 때 이미 구현된 클래스를 상속 받아서 속성이나 기능이 확장되는 클래스를 구현하는 할 때 상속을 이용한다. 그리고 상속을 할 때는 서로 이질적인 클래스간에는 상속을 하면 안된다.

위의 2번째 그림은 클래스 상속관계를 화살표로 표시하였고 1번째 그림은 클래스 상속관계를 수학에서 집합과 같이 벤다이어그램으로 표현하였다. 이처럼 클래스 간의 상속관계를 그림으로 표현한 것을 상속계층도라고 한다. 나중에 코딩을 하다보면, 상속관계가 복잡할 때가 있는데 이렇게 그림으로 천천히 그려서 확인해보면 이해가 빠를 것이다.
1번째 그림을 살펴보면 자식 클래스가 부모 클래스를 포함하고 있는데, 그 이유는 부모클래스에 메서드나 속성을 추가하면 자식클래스가 그것을 상속 받기 때문에 부모의 속성이나 메서드를 가지고 있다.

class Parent {
	int age;
}

class Child extends Parent {

}

위의 코드를 보면 Parent클래스를 Child 클래스가 상속받고 있으므로 Parent 클래스의 age도 Child 클래스가 내포하고 있는것과 같다.

상속을 사용하는 경우

  • 상위 클래스는 하위 클래스보다 일반적인 개념과 기능을 가짐
  • 하위 클래스는 상위 클래스보다 구체적인 개념과 기능을 가짐

⚠️ 주의
extends 뒤에는 단 하나의 class만 사용할 수 있다. 자바는 single inhertance만 지원한다.
C++이나 다른 객체지향언어를 배우신 분들은 헷길릴수 있다. C++에서는 다중상속을 지원하기 때문이다.

class Parent {
	int age;
}

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

위의 코드를 보면 Child클래스에 play()라는 메서드를 추가했다. 하지만 Parent클래스는 전혀 영향을 받지 않는다. 여기서 알 수 있는 것처럼 상위 클래스가 변경되면 하위 클래스는 자동적으로 영향을 받게 되지만, 그 반대는 아무런 영향을 주지 않는다. 그리고 상속을 계속 할 수록 멤버 개수는 점점 증가한다.

  • 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
  • 부모 클래스의 멤버 개수는 자식 클래스보다 항상 같거나 많다.

나중에 뒤에서 배우겠지만 접근제어자 (private, defaulr)가 붙은 멤버들은 상속은 되지만, 자식 클래스에서 접근이 불가능해서 마치 상속을 못 받은것처럼 보일 수 있다.

class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}

위의 그림과 코드를 보면 Cat 클래스와 Dog 클래스는 Animal이라는 상위 클래스를 상속 받고 있다. 즉, 고양이 클래스와 강아지 클래스는 동물이라는 상위클래스를 가졌다. 하지만, 같은 상위 클래스를 상속받는다 해서 Cat클래스와 Dog클래스가 무슨 관계가 있는 것이 아니다. 같은 상위 클래스를 상속받았다는 점만 같을 뿐이다. 이처럼 같은 내용의 코드를 상위 클래스에서 관리하면 코드의 중복이 줄어든다는 사실이다. 코드의 중복이 줄어들어서 유지보수 및 일관성을 유지시킬 수 있다. 만약 자신이 짜는 코드가 중복된 코드가 많다고 느껴지면 상속을 이용하는 것을 좀 고려를 해 볼 필요가 있다.

전체 프로그램을 구성하는 클래스들을 면밀히 설계분석하여, 클래스간의 상속관계를 적절히 맺어 주는 것이 객체지향 프로그래밍에서 가장 중요한 부분이다.

하위 클래스의 인스턴스를 생성하면, 상위 클래스의 멤버와 하위 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.

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

우리는 상속을 통하여 클래스를 재사용하는 방법을 알아봤다. 하지만 클래스 재사용하는 방법은 '포함관계'를 통해 재사용 할 수 있다. 클래스 간의 포함관계를 맺어 주는 것은 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것을 뜻한다. 아래의 코드를 보면 포함관계를 명확히 알 수 있을 것이다.

// 포함관계를 표현하지 않은 상태
class Circle {
	int x;
    int y;
    int r;
}
class Point {
	int x;
    int y;
}

// 포함관계를 사용한 상태
class Circle {
	Point p = new Point();
    int r;
}

이처럼 클래스 안에 멤버변수로 다른 클래스를 포함시키는 것은 매우 좋은 아이디어이다. 하나의 거대한 클래스를 만들어 가는것 보단, 여러개의 클래스로 나눠서 포함관계로 재사용하면 사용하는 입장에서 가시성이 좋을 것이다. 아래의 코드처럼 말이다.

class Car {
	Engine e = new Engine(); // 차는 엔진을 가지고 있다.
    Door[] d = new Door[4]; // 차는 문이 4개이다.
}

위 코드처럼 단위클래스별로 코드가 작게 나뉘어 작성되어 있기 때문에 코드를 관리하는데 수월할 것이다.

클래스간의 관계 결정하기

그러면 지금 많이 헷갈릴 것이다. "그래서? 언제 상속관계로 해주고 언제 포함관계로 해줘야해?"

class Circle {
	Point p = new Point();
    int r;
}
class Circle extends Point {
	int r;
}

위의 코드들 중에 어떤 걸로 해야할까? 정답은 바로 관계를 보는 것이다.

자식클래스는 부모클래스이다. (IS-A 관계) -> 상속
자식클래스는 부모클래스를 가지고 있다. (HAS-A 관계) -> 포함

그러면 바로 적용해보자. '원은 점이다.'가 맞을까? '원은 점을 가지고 있다'가 맞을까? 정답은 후자이다.
IS-A관계는 상속관계를 이용하면 되고. HAS-A관계는 포함관계를 이용하면 된다. 원과 점 클래스는 HAS-A관계가 맞으므로 포함관계를 이용하면 된다.

하지만 IS-A관계, HAS-A관계가 항상 맞지는 않다. 뭔가 논리적으로 명확하지 않은 경우가 있다.
일단 먼저 이 관계들을 적용해보고 논리적으로 더 맞는것을 선택해보자. 그러면 왠만하면 필터링이 될것이다.

toString()

코드를 작성하다보면 확인용도로 객체의 정보를 출력할 때가 있다.

Car c = new Car();

System.out.println(c); // System.out.println(c.toString());과 같다.

위처럼 코드를 실행하면 객체의 주소값이 출력이 될것이다. 하지만 우리가 원하는 것은 객체의 주소가 아니라 객체안에 멤버변수들의 정보이다. 이럴때 toString()을 재정의하는 것이다. 원래 toString()은 인스턴스의 정보를 문자열로 반환할 목적으로 정의된 것이다. 참조변수의 출력이나 덧셈연산자를 이용한 참조변수와 문자열의 결합에는 toString()이 자동적으로 호출되어 참조변수를 문자열로 대치 후 처리한다.

단일 상속

다른 객체지향언어인 C++에서는 다중상속이 가능하다. 하지만, 자바에서는 단일 상속만 허용한다.
막상보면 다중상속이 여러 부모 클래스를 상속받아서 확장성에 뛰어나다고 복합적 기능을 만들 수 있다는 장점만 보일 것이다. 하지만, 클래스 간의 관계가 매우 복잡해지며, 서로 다른 클래스로부터 상속받은 멤버간의 이름이 같을 경우 구별할 수 있는 방법이 없다는 단점을 가지고 있다.
단일 상속이 하나의 부모 클래스만을 가질 수 있기 때문에 다중상속에 비해 불편한 점도 있지만 클래스 간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있게 만들어 준다는 점에서 다중상속보다 유리하다.

만일 다중상속을 정말 할 상황이라면 IS-A관계 혹은 HAS-A관계를 생각해서 하나는 상속받고 하나는 포함관계를 하면 다중상속과 같은 비슷한 효과를 볼 수 있다.

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

Object클래스는 모든 클래스의 최상위에 있는 부모클래스이다. 쉽게 생각해서 클래스계 단군할아버지라고 생각하면 될것이다. 아래와 같이 Tv라는 클래스를 정의했다고 해보자.

class Tv {
}

여기서 Object클래스가 최상위 부모클래스라고 했으니 뒤에 extends 키워드 붙이고 뒤에 Object를 붙여줘야하는거 아닐까? 정답은 맞다. 컴파일 단계에서 자동으로 extends Object를 붙여준다.
즉, 위의 코드가 아래의 코드처럼 바뀐다.

class Tv extends Object {
}

이렇게 함으로 Object클래스가 모든 클래스의 조상이 되도록 한다.
모든 상속계층도의 최상위에는 Object 클래스가 위치한다. 그래서 우리가 아까 toString()이나 equals(Object o)와 같은 메서드를 사용할 수 있던 것이다. 이 메서드들은 Object 클래스에 정의되어 있기 때문이다.

참고로, 이미 어떤 클래스로부터 상속받도록 extends되어 있는 클래스는 컴파일러가 따로 extends Object를 붙여주지 않는다. 그 이유는 그 상속하려는 부모클래스가 Object를 상속받기 때문이다.

profile
모든 것을 즐길줄 아는 개발자입니다!

0개의 댓글