상속이란?
기존 클래스의 데이터를 재사용하여 새로운 클래스로 작성
흔히 대부분 클래스간 상속 관계에서 상속하는 클래스는 부모 클래스, 상속 받는 클래스는 자식 클래스라고 합니다
상속을 사용하는 이유?
적은 양의 코드로 새로운 클래스를 작성할 수 있다. 즉, 코드의 재사용성을 높이고 중복을 제거합니다. 따라서 프로그램의 생상선과 유지보수에 크게 기여할 수 있습니다.
왜 관리가 쉬워질까?
자식은 부모의 유전자(클래스 데이터)를 가지고 있다. 따라서 공통적인 부분(부모 데이터)은 부모의 유전자에서 관리하고 자식은 자신이 새로 개발한 유전자(자신에 정의된 멤버)를 관리하면 된다.
포함 관계
포함 관계란 클래스의 멤버변수로 다른 클래스가 존재하는 관계이다.
상속 관계: is-a (~은 ~이다.)
포함 관계: has-a (~은 ~을 가지고 있다.)
단일 상속
Java는 C++과는 다르게 다중 상속을 금지합니다.
왜 금지할까?
다중 상속에 대해서 주는 이점은 여러 클래스를 상속 받을 수 있기 때문에 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있다. 다만 여기서 발생하는 근본적인 문제가 발생합니다. 바로 Diamond problem입니다.
Diamond problem이란?
위와 같이 상속 받는 상태에서 diamond problem이 발생됩니다.
두 parent클래스는 각각 자신의 run클래스를 가지며 이름과 매개변수만 같은 상태에서 메서드 내용이 다른 상태입니다.
이때 Test클래스가 두 부모 클래스를 상속 받는다면 run이라는 메서드를 어떻게 구분할까?에서 문제가 발생합니다.
만약, run()이라는 함수가 static하여 클래스 메서드라 생각하면 구분할 수 있지만, 인스턴스 메서드의 경우 두 함수를 구분할 방법이 없다. 따라서 이를 해결하기 위해서는 매개변수를 변경하거나 메서드 이름을 변경해야 합니다. 이렇게 되면 run 메서드를 사용하는 모든 클래스들을 변경해야 한다.
따라서 자바는 다중상속의 장점을 포기하고 단일 상속만 허용한다.
따라서 클래스 간의 관계가 더욱 명확해 진다는 점을 추구했습니다.
그렇다면 다중 인터페이스는?
다중 인터페이스
클래스에 대한 다중 상속에 대해서는 불가능하지만 다중 인터페이스에 대해서는 가능하지 않을까?
맞습니다. 다중 인터페이스의 경우 기본 메서드는 구현을 하지 않은 상태이므로 해당 메서드를 implement하여 사용할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
interface TEST1 {
void run();
}
interface TEST2 {
void run();
}
class Ex implements TEST1, TEST2{
@Override
public void run() {
System.out.println("RUN");
}
public static void main(String[] args) {
Ex ex = new Ex();
ex.run();
}
}
|
그렇다면 Java 1.8이상에서 도입된 default method는?
이 또한 가능합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
interface TEST1 {
default void run() {
System.out.println("Default TEST 1");
}
}
interface TEST2 {
default void run() {
System.out.println("Default TEST 2");
}
}
class Ex implements TEST1, TEST2{
public void run() {
TEST1.super.run();
TEST2.super.run();
}
public static void main(String[] args) {
Ex ex = new Ex();
ex.run();
}
}
|
또는
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
interface TEST {
default void run() {
System.out.println("Default TEST");
}
}
interface TEST1 extends TEST{
}
interface TEST2 extends TEST{
}
class Ex implements TEST1, TEST2{
public static void main(String[] args) {
Ex ex = new Ex();
ex.run();
}
}
|
Object 클래스
모든 클래스의 조상은 Object 클래스이다. 대표적인 메서드가 toStirng, equals이고 모든 클래스가 이와 같은 메서드를 사용할 수 있는 이유는 자신의 최상위 클래스는 Object 클래스이기 때문이다.
Overriding
부모 클래스로부터 상속받은 메서드의 내용을 변경하는 것
예를들면,
도형이라는 클래스를 상속받는다 생각하자
- 상속 받는 클래스는 삼각형 정사각형, 직사각형이 있다고 생각하자
- 도형에는 넓이는 구하는 메서드가 있다
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
40
41
42
43
44
45
46
47
|
class Shape {
int width;
int height;
Shape () {}
Shape(int width, int height) {
this.width = width;
this.height = height;
}
double calcArea() {
return width * height;
}
}
class Square extends Shape{
Square (int width, int height) {
super(width, height);
}
}
class Rectangle extends Shape{
Rectangle (int width, int height) {
super(width, height);
}
}
class Triangle extends Shape {
Triangle (int width, int height) {
super(width, height);
}
@Override
double calcArea() {
return width * height / 2;
}
}
class Main {
public static void main(String[] args) {
Shape square = new Square(4,4);
Shape tri = new Triangle(4,4);
Shape rect = new Rectangle(4,5);
System.out.println(square.calcArea());
System.out.println(tri.calcArea());
System.out.println(rect.calcArea());
}
}
|
위와 같을 때 삼각형만 정사각형과 직사각형이랑은 넓이를 구하는 방식이 다르다. 이렇게 동일한 함수로 다른 동작을 할때 사용한다.
오버라이딩의 조건
- 메서드의 이름이 같아야 한다.
- 매개변수가 같아야 한다.
- 반환 타입도 같아야 한다.
- 접근 제어한자는 변경할 수 있다. 다만 더 넓은 범위로 변경해야 한다. (부모:protected -> 자식: protected or public)
- 부모 클래스의 메서드보다 많은 수의 예외 선언 불가
Super
super는 자신이 아닌 부모 클래스를 지칭하는 명령어이다. super는 자식 클래스의 멤버와 부모 클래스의 멤버를 구분해야 할 경우 사용한다. 기본적으로 부모 클래스를 상속받으면 멤버 변수도 자신의 멤버이므로 this를 사용할 수 있다.
예를 들면 위 코드에서 34번째 줄에서 return width * height / 2대신 똑같이 넓이를 계산하는 calcArea()가 부모 클래스에 있으므로 return super.calcArea() / 2;로 변경할 수 있다.
super()
super()는 this()와 같이 생성자이다. 다만 부모 클래스의 생성자를 부르는 것입니다.
단, 생성자는 하나의 함수이다. new를 통해 새로운 객체를 메모리에 올린다.
위 코드에서 생성자를 통해 값을 전달하여 생성 했을때 super를 사용하여 부모 클래스를 통해 초기화 하였다.
사실 상속을 했다는 것은 부모 클래스의 멤버를 자식도 가지고 있어서 사용 가능하다 생각하면 된다.
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
|
class Shape {
int width;
int height;
Shape(int width, int height) {
this.width = width;
this.height = height;
}
double calcArea() {
return width * height;
}
}
class Rectangle extends Shape{
Rectangle (int width, int height) {
super(width, height);
}
}
class Triangle extends Shape {
Triangle (int width, int height) {
this.width = width;
this.height = height;
}
@Override
double calcArea() {
return super.calcArea() / 2;
}
}
|
만약 여기서 처럼 Shape에 대해 기본 생성자가 없고 Triangle 클래스에서 자기 자신의 멤버에 값을 초기화 한다면 어떨까?
에러가 난다
왜냐하면 Traingle 클래스에서 생성자를 통해 인스턴스를 생성하게 된다면 23번째 줄을 시작하기 전에 부모 클래스의 생성자에 먼저 접근하게 된다 이렇게 최상위 클래스인 Object클래스까지 가서 모든 함수를 처리하고 다시 오는 과정이다.
Triangle(int, int)클래스 -> Shape()클래스 -> Object()
다만 여기서 Shape에 대한 default 생성자가 없기 때문에 접근하지 못하고 에러를 내는 것이다.
Rectangle(int, int)클래스 -> Shape(int, int)클래스 -> Object()
rectangle클래스의 경우 Shape(int, int)클래스가 존재하여 에러를 내지 않고 갈 수 있다.
'JAVA > 자바스터디' 카테고리의 다른 글
상속 - 다형성 (0) | 2022.11.17 |
---|---|
JAVA - 제어자 (0) | 2022.11.14 |
[자바스터디] 5. 클래스 (0) | 2022.01.06 |
[자바스터디] 4. 선택문, 반복문 (0) | 2022.01.06 |
[자바스터디] 3. 연산자 (0) | 2022.01.03 |