상속은 무엇이며, 어떻게 사용하는 것일까?
상속 (Inheritance)
--
상속은
기존의 클래스를 재사용하고 추가로 확장하여 새로운 클래스를 생성하는 방법으로
코드의 재사용성을 높이고, 클래스 간의 관계도 형성할 수 있게 된다.
상속 기본 개념(용어)
- 부모 클래스 : 재사용할 기존 클래스 (상위 클래스, 슈퍼 클래스라고도 부른다.)
- 자식 클래스 : 부모 클래스를 가지고 확장하여 새로 생성한 클래스 (하위 클래스, 서브 클래스라고도 부른다.)
상속해 주는 클래스를 "부모 클래스"
상속받는 클래스를 "자식 클래스"라고 생각하면 된다.
상속 특징
- 단일 상속만 가능 (자식 클래스는 하나의 부모 클래스만 상속 가능)
- 자식 클래스는 부모 클래스의 멤버(변수, 메서드)만 상속받는다. (생성자나 초기화 블록은 상속 X)
단일 상속만 가능하지만
자식 클래스를 또 다른 클래스에게 상속하는 것은 가능하다.
상속 기본 형식 (extends 키워드를 사용)
class 상속받는 클래스명 extends 상속해줄 클래스명 {
}
class Parent {
String name;
int age;
void introduce() {
System.out.println(name + ", " + age);
}
}
class Child extends Parent {
void play() {
System.out.println("노는 시간~");
}
}
즉, Child 클래스에서 부모 클래스인 Parent의 멤버(name, age, introduce())를 사용할 수 있다.
상속의 장단점
- 코드 재사용성 : 부모 클래스의 코드를 재사용
- 유지보수 : 공통 기능을 부모 클래스에 정의하여 한 번에 수정 적용 가능
- 확장성 : 새로운 기능을 추가하거나 수정할 때 상속을 통해 확장 가능
- 복잡성 증가 : 상속 구조가 깊어질수록 관계가 복잡
- 강한 결함 구조 : 자식 클래스는 부모 클래스에 의존
--
super, super()
--
super와 super()는
this와 this()랑 비슷한 느낌으로
this와 this()는 현재 클래스의 이름을 대신하여 사용하는 키워드고,
super와 super()는 현재 클래스의 부모 클래스 이름을 대신하여 사용하는 키워드다.
- super : 부모 클래스의 이름을 대신하여 사용
- super() : 부모 클래스의 생성자를 대신하여 사용
class Parent {
String name = "Parent";
int age = 20;
Parent() {
System.out.println("부모 생성자 호출됨");
}
void display() {
System.out.println("여기는 부모 클래스");
}
}
class Child extends Parent {
String name = "Child";
Child() {
super() // 부모 클래스의 생성자 Parent() 호출
System.out.println("자식 생성자 호출됨");
}
void display() {
System.out.println("여기는 자식 클래스");
}
void printInfo() {
System.out.println(name); // 자식 클래스의 name
System.out.println(super.name); // 부모 클래스의 name
System.out.println(age); // name 필드와 달리 부모 클래스에만 존재하는 필드는 super 생략 가능
display(); // 자식 클래스의 display() 호출
super.display(); // 부모 클래스의 display() 호출
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.printInfo();
}
}
/* 결과
부모 생성자 호출됨
자식 생성자 호출됨
Child
Parent
20
여기는 자식 클래스
여기는 부모 클래스
*/
super키워드는
부모 클래스의 필드 이름과 자식 클래스의 필드 이름이 동일한 것이 있다면
이를 구분하기 위해 사용되는 키워드로
만약 동일한 필드가 없다면 super를 생략해도 부모의 필드를 자동으로 탐색하여 사용할 수 있다.
(물론 super를 사용해도 된다.)
super()키워드는
this()처럼 자식 클래스의 생성자에서 반드시 첫 번째 줄에서 호출해야 한다.
(this()와 super()는 동시에 작성 불가 / 생성자 안에 둘 중 하나만 작성 가능)
자식 클래스로 객체를 생성하면 해당 부모 클래스의 필드들을 사용할 수 있는 이유?
클래스를 사용하기 위해서는 생성자를 호출하여 메모리에 할당해야 사용할 수 있다.
즉, 자식 클래스로 객체를 생성하면 해당 부모 클래스의 생성자도 호출된다는 것이다.
자식 클래스의 생성자를 호출하여 객체를 생성할 때 부모 클래스의 생성자도 호출해야 하기 때문에
자식 클래스의 생성자에는 부모 클래스의 생성자를 호출하는 super() 메서드가 꼭 있어야 한다.
만약 없다면 부모 클래스의 필드 정보가 메모리에 할당되지 않아 사용할 수 없게 된다.
여기서 부모 클래스의 생성자를 호출하는 것은 새로 부모 클래스의 객체를 생성하는 것이 아니라
자식 클래스의 필드들이 저장된 힙 메모리에 추가로 부모 필드도 할당되는 것이다.
그래서 최상위 클래스인 Object 클래스를 제외하고는 모든 클래스의 생성자에는 super() 메서드가 존재하여
부모 클래스의 부모 클래스의 부모 클래스 ... 최종적으로 최상위 부모 클래스인 Object 클래스까지 생성자를 호출하여
모두 사용할 수 있게 되는 것이다.
만약 생성자에 super() 메서드를 까먹고 작성하지 않을 경우를 대비하여
컴파일러는 super() 메서드 생략 시 부모 클래스의 기본 생성자를 호출하는 super() 메서드를 자동으로 추가해 준다.
여기서 주의해야 할 점은
자동으로 추가해 주는 super()는 부모 클래스의 기본 생성자를 호출하는 것이다.
만약 해당 부모 클래스에 기본 생성자가 없다면 부모 클래스를 호출하지 못하니 에러가 발생하게 된다.
그래서 그냥 자동으로 추가해 주는 super()를 사용하지 말고
부모 클래스의 생성자 종류에 대해 잘 파악하고 super()를 작성하도록 하자.
super()를 자동으로 추가해 주는 조건은
해당 생성자에 다른 생성자를 호출하는 메서드가 없을 경우에 자동으로 추가된다.
만약 다른 생성자를 호출하는 코드가 있다면 super()는 추가되지 않는다.
super() 메서드는 생성자의 맨 첫 줄에 작성해야 하는 이유는?
객체 생성 시 부모 클래스의 필드 초기화와 객체 초기화의 순서와 관련이 있는데
자식 클래스는 부모 클래스의 필드를 가지고 재사용하여 확장한 클래스로
먼저 부모 클래스의 필드를 먼저 초기화를 시켜주는 순서로 진행해야 하기 때문이다.
만약 자식 클래스 먼저 초기화를 시켜주는 방식으로 진행된다면?
자식 클래스에서 부모 클래스의 필드를 참조하는 상황이 있다면
해당 부모 플래스의 필드는 아직 존재 여부를 모르기 때문에 오류가 발생할 수 있다.
class Point {
int x;
...
Point() { // 다른 생성자를 호출하여 super() 자동 추가 X
this(5, 3);
}
Point(int x, int y) { // 다른 생성자를 호출하지 않아 super()가 자동 추가 O
// super();
this.x = x;
}
}
class Point3D extends Point {
int z;
...
Point3D(int x, int y, int z) { // 다른 생성자를 호출하지 않아 super()가 자동 추가 O
// super();
this.x = x;
this.y = y;
this.z = z;
}
}
위 코드에서 만약 Point() 기본 생성자가 없다면
Point3D(int x, int y, int z) 생성자에서 기본으로 추가된 super()로 Point() 호출을 못하니
에러가 발생하게 된다.
그래서 부모 클래스의 생성자를 고려하지 않고
자동으로 추가되는 super()를 사용하면 에러가 발생할 수 있다.
--
포함 관계
--
포함 관계는
상속 외에도 클래스를 재사용하는 방법으로
클래스에 멤버 변수로 다른 클래스를 객체 생성하여 재사용하는 방법이다.
class Point {
int x;
int y;
}
class Circle extends Point { // 상속 관계
int z;
}
class Circle { // 포함 관계
Point p = new Point();
int z;
}
/*
class Circle {
int x; // p.x
int y; // p.y
int z;
}
처럼 사용이 가능해진다.
*/
상속 관계와 포함 관계는 큰 차이가 없어 보이지만
세부적으로 따지면 느낌이 다르다.
상속은 [ is-a ] 관계로
부모 클래스의 특성을 상속받아 사용하는 자식 클래스의 관계다.
즉, 크게 기본 틀(부모 클래스)이 존재하며 해당 기본 틀로부터 파생된 무언가(자식 클래스)의 관계다.
(개(자식)는 동물(부모)이다.)
포함은 [ has-a ] 관계로
해당 클래스는 특정 객체를 포함하고 있는 관계다.
즉, 큰 무언가(클래스) 안에 특정 어떤 것(객체)이 포함되어 있는 관계다.
(자동차(클래스) 안에 엔진(객체)을 포함하고 있다.)
상속은 아래 코드처럼 다중 상속이 불가능하고 단일 상속만 가능하다.
class Test extends Aaaa, Bbbb { // 에러!!
}
하지만 그래도 다중 상속을 하고 싶다면 포함 관계를 이용하면 비슷하게나마 구현할 수 있다.
class Test extends Aaaa {
Bbbb b = new Bbbb();
}
--
Object 클래스
--
Object 클래스는
모든 클래스들의 최상위 부모 클래스다.
자바에서 작성된 모든 클래스는 암묵적으로 Object 클래스를 상속받는 구조로 이루어져 있으며,
이를 통해 모든 클래스는 기본적으로 Object 클래스의 기능을 사용할 수 있게 된다.
그래서 모든 클래스에서는
Object 클래스에서 제공하는 toString(), equals()와 같은 메서드를 사용할 수 있게 되는 것이다.
클래스를 작성할 때 상속을 받지 않도록 하면 자동으로 Object 클래스를 상속받게 된다.
만약 상속을 받는 자식 클래스는?
상속은 단일 상속만 가능하기 때문에 특정 부모 클래스를 상속받도록 작성하게 되면
해당 클래스에는 자동으로 Object 클래스를 상속하지 않는다.
다만, 계속 해당 부모 클래스로 거슬러 올라가 보면 최종 부모 클래스는 상속을 받지 않는 부모 클래스일 것이고,
해당 클래스는 자동으로 Object 클래스를 상속받게 되니까
결국 자식 클래스도 Object 클래스를 상속받는 형태가 된다.
class aaa { // 사실 class aaa extends Object { ... }
...
}
class bbb extends aaa {
...
}
class ccc extends bbb{
...
}
--
'Language > Java' 카테고리의 다른 글
package와 import (+ static import) (1) | 2024.10.19 |
---|---|
[클래스] 변수와 메서드 (+ 초기화 블록) (0) | 2024.10.18 |
오버로딩, 오버라이딩 (0) | 2024.10.16 |
생성자 (+ new, this, this() 키워드) (0) | 2024.10.13 |
클래스, 객체, 인스턴스 (+ 객체 생성 방법) (2) | 2024.10.12 |