일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- array
- string
- Method
- 코테
- Counting
- Data Structure
- database
- Binary Tree
- two pointers
- java
- dynamic programming
- bit manipulation
- simulation
- Math
- hash table
- SQL
- Number Theory
- Class
- implement
- 코딩테스트
- Tree
- greedy
- Matrix
- sorting
- Binary Search
- Stack
- 자바
- 파이썬
- 구현
- geometry
- Today
- Total
코린이의 소소한 공부노트
클래스 간의 관계 - 상속, 포함 본문
일반적으로 쓰이는 상속은 부모의 것을 자식이 물려받는 것을 말하고, 포함은 어떤 범주에 들어가 있거나, 물리적으로 어떤 것 안에 존재하는 것 등을 말한다. 이 말이 클래스 간에도 쓰인다.
[상속]
1. 상속의 뜻
- 기존의 클래스로 새로운 클래스를 작성하는 것 -> 코드 재사용성 상승
- 두 클래스를 부모와 자식으로 관계를 맺어주는 것 -> (자식 -> 부모) 형태로 그리게 됨
- 부모 클래스를 확장해 자식 클래스를 만드는 개념 -> 키워드로 extends를 사용
class 부모클래스 {
// 변수1, 메서드1 정의
}
class 자식클래스 extends 부모클래스{
// 변수2, 메서드2 정의
}
// 자식클래스는 부모클래스의 변수1, 메서드1을 물려 받는다
2. 상속의 특징
1) 자손은 조상의 모든 멤버를 상속받는다. (생성자, 초기화 블록 제외)
- A -> B -> C라면, A는 B와 C의 모든 멤버를 상속 받음
2) 자손의 멤버 개수는 조상보다 같거나 많다.
class B { // B의 멤버는 1개
int x;
}
class A extends B { }
// A 내부에는 아무것도 없지만
// A의 멤버는 1개
3) 조상의 변경은 자손에 영향을 미치지만, 자손의 변경은 조상에 영향을 미치지 않는다.
// 1. 조상의 변경
class B { // B의 멤버가 2개로 변경됨
int x;
int y;
}
class A extends B { }
// A의 멤버도 2개로 변경
// 2. 자손의 변경
class A extends B { // A에 멤버 1개 추가
void method1(){ }
}
// A의 멤버는 3개
// B의 멤버는 여전히 2개
상속을 받는 것과 받지 않는 것은 메모리상에 어떤 차이가 있을까? 이것을 확인해보기 위해 2차원 좌표를 나타내는 Point 클래스를 하나 만든 다음, 3차원 좌표를 나타내는 Point3D 클래스를 2가지 버전으로 만들어보기로 했다.
class Point { // 2차원 좌표
int x;
int y;
} // 2개의 멤버
class Point3D1{ // 3차원 좌표 #1. Point클래스와 관계 없음
int x;
int y;
int z;
} // 3개의 멤버
class Point3D2 extends Point{ // 3차원 좌표 #2. Point클래스 상속받음
int z;
} // 부모2 + 자식1 = 3개의 멤버
이제 2가지 Point3D로 각각 객체를 생성한 후의 메모리 상태를 그려보겠다.
분명 다른 클래스로 객체를 생성했으나 구조가 같다. 멤버 변수의 개수와 이름이 같기 때문이다. 별반 차이가 없다면 우리는 코드 중복을 제거하고 재사용성을 높인 Point3D2처럼 설계하는 편이 더 좋겠다.
[포함]
1. 포함의 뜻
- 클래스의 멤버로 참조변수를 선언하는 것
예시로 원을 나타내는 Circle 클래스를 작성하려 한다. 원을 나타낼 때는 중심의 좌표와 반지름의 길이가 필요한데, 이를 그냥 작성한 것과 포함을 이용한 것 2가지 버전으로 만들어 보겠다.
class Circle1 { // 1번. 필요한 내용을 각각 멤버 변수로 작성
int x; // 중심의 x좌표
int y; // 중심의 y좌표
int r; // 반지름의 길이
}
class Circle2{ // 2번. 중심의 좌표를 Point 클래스로 표현
Point c = new Point(); // Circle2가 Point를 포함
int r;
}
이것도 메모리상에서 어떤 차이가 있는지 각각 객체 생성을 한 후의 메모리 상태를 그려보겠다.
저장공간이 3개인 것은 같지만, 구조가 다르다. c1은 멤버변수가 int형 3개지만, c2는 멤버변수가 int형 1개에 참조형 1개이기 때문이다. 이름은 x, y, r로 같긴 하나 객체 생성 후 접근방법에서 차이가 난다.
x | y | r | |
c1 | c1.x | c1.y | c1.r |
c2 | c2.c.x | c2.c.y | c2.r |
[상속과 포함의 비교]
1. 상속은 클래스의 상하위 개념이 뚜렷할 때 사용하고, 포함은 작은 단위의 클래스를 조합할 때 사용한다.
1) 상속 예시
a. 무지, 춘식이, 튜브는 카카오프렌즈다.
b. 스마트tv는 tv의 한 종류다.
2) 포함 예시
c. 원이란 한 점(중심)에서 같은 거리(반지름)만큼 떨어진 점들의 집합이다.
d. 차의 특징에는 엔진, 기어, 문의 개수 등을 들 수 있다.
2. 상속은 "~는 ~이다 (is-a)"로 해석되고, 포함은 "~는 ~를 갖고 있다 (has-a)"로 해석된다.
- 상속에서는 앞 클래스가 자식 클래스, 뒷 클래스가 부모 클래스이다.
- 포함에서는 앞 클래스가 큰 클래스, 뒷 클래스가 작은(포함되는) 클래스이다.
is-a 해석 | has-a 해석 | 자연스러운 해석 | ||||||
a | 무지는 카카오프렌즈이다 | 카카오프렌즈는 무지를 갖고 있다 | is-a | 상속 | ||||
b | 스마트tv는 tv다 | tv는 스마트tv를 갖고 있다 | is-a | 상속 | ||||
c | 점은 원이다 | 원은 점을 갖고 있다 | has-a | 포함 | ||||
d | 엔진은 차다 | 차는 엔진을 갖고 있다 | has-a | 포함 |
해석으로 상속과 포함을 알았으니, 대략적으로 코드를 써보자면 다음과 같다.
// 상속
class KakaoFriends { }
class Muzi extends KakaoFriends{ }
class Tv {}
class SmartTv extends Tv {}
// 포함
class Point {}
class Circle {
Point p = new Point();
}
class Engine {}
class Car{
Engine e = new Engine();
}
# 참조변수 초기화
- 포함된 클래스는 되도록 new까지 이용해 객체 생성을 해야 한다.
- 저장공간이 없는 객체는 의미가 없기 때문이다.
- 위의 Car 클래스에서 Engine e;만 쓸 거라면 생성자에서라도 new를 이용해 객체를 생성해줘야 한다.
3. 상속이 사용되는 경우는 10% 정도고, 나머지 90%는 포함을 사용한다.
- 자바에서는 다중 상속을 허용하지 않기 때문에 대부분이 포함관계이다.
다중 상속은 1개의 자식 클래스가 여러 개의 부모 클래스로부터 상속받는 것을 말한다. c++에서는 다중 상속을 허용하지만, 자바에서는 단일 상속(single inheritance)만 허용한다. 즉, 각 클래스 당 상속받을 수 있는 클래스는 최대 1개라는 것이다.
단일 상속만 허용하는 이유는 충돌 문제를 방지하기 위해서다. 예를 들어, 자바에서 다중 상속을 허용해서 다음과 같이 코드를 작성했다고 생각해보자.
class Chicken {
void eat() {
System.out.println("치키니 냠냠~!");
}
}
class Pork {
void eat() {
System.out.println("오겹살 냠냠~!");
}
}
class Muzi extends Chicken, Pork{ }
// 메인 메서드에서..
Muzi mz = new Muzi();
mz.eat(); // ???
Muzi는 2개의 클래스를 상속받았기 때문에 사용 가능한 eat 메서드가 2가지다. 그런데 두 메서드는 이름만 같지 내용은 전혀 다르다. 그래서 메인에서 eat 메서드를 호출했을 때 어떤 eat 메서드를 호출해야 할지 당황하게 된다.
이처럼 다중 상속을 허용하면 부모 클래스에 다른 기능을 하는 같은 이름의 메서드가 존재할 때 어느 메서드를 상속받아 쓸지 알 수 없다. 하지만 다중 상속의 편리함을 포기할 수 없기에, 인터페이스를 이용해 충돌 문제를 해결하고 다중 상속과 같은 기능을 구현했다. 인터페이스에 관한 글은 추후 게시 예정이다.
인터페이스가 없어도, 상속문제를 어느 정도 해결할 수는 있다. 예시를 들면서 설명해보겠다.
옛날 옛적에 MZ전자에서 tv에 dvd를 달아서 TVD를 출시하려고 했다. 무지는 Tv와 DVD클래스를 상속받아서 TVD클래스를 만들어보려 했다.
class TVD extends Tv, DVD { } // error: Syntax error on token ","
예상 밖의 에러 메시지가 나왔다. 쉼표 때문에 문법 에러가 걸린 것이다. 애초에 자바는 다중 상속을 생각하지 않기 때문에 "어라, 사용자 너님 오타 낸 거 같음?!"이라는 식의 에러 메시지를 출력하는 것이다.
무지는 고민에 빠졌다. 어떤 것을 상속받고 어떤 것을 포함시키지? 그래서 클래스를 하나하나 살펴보기로 했다.
class Tv {
boolean power;
int channel;
void power() { power = !power; }
void channelUp() { ++channel; }
void channelDown() { --channel; }
}
class DVD {
boolean power;
void power() { power = !power; }
void play() { // 생략 }
void stop() { // 생략 }
void rew() { // 생략 }
void ff() { // 생략 }
}
클래스 멤버 변수를 살펴본 무지는 주 기능은 Tv클래스에 있다고 판단을 내렸다. 그래서 Tv 클래스의 모든 기능을 상속받아서 쓰고, DVD 클래스는 포함으로 해서 나머지 메서드를 필요시 호출하게끔 코드를 고치기로 했다.
class TVD extends Tv{
DVD d = new DVD();
void play() { d.play(); }
void stop() { d.stop(); }
void rew() { d.rew(); }
void ff() { d.ff(); }
}
// Tv에 power 메서드가 있으므로
// DVD의 power는 쓸 필요 없음
-> 비중이 높은 클래스 하나만 상속으로 하고, 나머지는 포함을 이용한다.
[쿠키글] Object클래스 - 모든 클래스의 조상
자바는 단일 상속을 허용하기 때문에 부모 클래스를 1개만 쓸 수 있고, 안 쓸 수도 있다. 하지만 모든 클래스는 다 1개의 부모 클래스가 있다. 다들 예상했겠지만 컴파일러 짓이다.
컴파일러는 부모가 명시되어있지 않은 클래스는 자동적으로 Object클래스를 상속받게 만든다.
// 컴파일 전
class Tv {}
class SmartTv extends Tv {}
// 컴파일 후
class Tv extends Object {}
class SmartTv extends Tv {}
SmartTv는 Tv를 상속받았고, Tv는 Object를 상속받았기 때문에 SmartTv는 Object의 멤버를 사용할 수 있다.
모든 클래스의 조상은 Object 클래스이고, 모든 클래스는 Object클래스에 정의된 11개의 메서드를 상속받게 된다.
- 메서드: toString(), equals(Object obj), hashCode(), ...
이 중 가장 많이 본 toString 메서드 사용을 예로 들겠다.
SmartTv s = new SmartTv();
System.out.println(s.toString()); // SmartTv@7637f22
System.out.println(s); // SmartTv@7637f22
s는 객체이기 때문에 toString을 이용하면 클래스이름(객체타입)@객체주소가 나오게 된다. 그런데 그냥 s만 출력해도 같은 값이 나온다. 이는 SmartTv가 Object의 자손이기 때문에, 아무것도 쓰지 않아도 컴파일러가 자동으로 toString을 추가해주는 것이다.
Object클래스에 정의된 11개의 메서드가 궁금한 사람(그게 나야~)도 있을까 봐, 무엇인지 찾아봤다.
그만 알아보자..
'Java' 카테고리의 다른 글
생성자 super() vs 참조변수 super (0) | 2022.03.23 |
---|---|
메서드 오버라이딩 (+오버로딩과의 차이점) (0) | 2022.03.22 |
변수의 초기화 (0) | 2022.03.12 |
생성자 this() vs 참조변수 this (0) | 2022.03.12 |
생성자 (0) | 2022.03.12 |