상속은 여러 클래스에서 공통적인 속성(필드)과 기능(메서드)을 부모 클래스에 정의하고, 이를 자식 클래스가 물려받아 사용하는 개념이다. 이를 통해 코드의 중복을 제거하고, 유지보수를 쉽게 하며, 재사용성을 높일 수 있다.
Cat
은 Animal
의 한 종류이므로 Cat
클래스는 Animal
클래스를 상속할 수 있다.하지만, 부모 클래스의 모든 멤버를 물려받아도 접근 권한에 따라 자식 클래스가 직접 접근할 수 있는 멤버가 제한될 수 있다.
private
필드나 메서드는 자식 클래스에서 직접 접근할 수 없다.default
접근 제한자는 상속받은 클래스가 다른 패키지에 있을 경우 접근할 수 없다.이를 해결하기 위해 getter와 setter 메서드를 사용하여 필드에 간접적으로 접근할 수 있다.
extends
키워드를 사용하여 상속을 구현한다.
class 자식클래스 extends 부모클래스 {
// 자식 클래스 코드
}
자식 클래스는 부모 클래스의 메서드와 필드를 물려받는다.
단일 상속만을 허용힌다.
코드의 중복성 제거
다형성 ⭐️
class Parent {
String p = "parent";
void viewParent() {
System.out.println("viewParent() 호출");
}
}
class Child2 extends Parent {
String c2 = "child2";
void viewChild() {
System.out.println("viewChild2() 호출");
}
}
class GrandChild1 extends Child2 {
String gc2 = "grandChild2";
void viewGrandChild() {
System.out.println("viewGrandChild1() 호출");
}
}
public class InheritanceEx01 {
public static void main(String[] args) {
GrandChild1 gc = new GrandChild1();
gc.viewGrandChild(); // 자식 클래스의 메서드 호출
gc.viewChild(); // 부모 클래스의 메서드 호출
gc.viewParent(); // 조부모 클래스의 메서드 호출
// 필드 접근
System.out.println(gc.p); // Parent의 필드 사용
System.out.println(gc.c2); // Child2의 필드 사용
System.out.println(gc.gc2); // GrandChild1의 필드 사용
}
}
Parent
→ Child2
→ GrandChild1
순으로 계층 구조를 이루며, 자식 클래스는 상위 클래스의 모든 멤버를 상속받는다.class Parent {
Parent() {
System.out.println("Parent 생성자 호출");
}
void viewData1() {
System.out.println("Parent viewData1() 호출");
}
}
class Child1 extends Parent {
Child1() {
System.out.println("Child1 생성자 호출");
}
// 오버라이딩
void viewData1() {
System.out.println("Child1 viewData1() 호출");
}
}
class Child2 extends Parent {
Child2() {
System.out.println("Child2 생성자 호출");
}
// 오버라이딩
void viewData1() {
System.out.println("Child2 viewData1() 호출");
}
}
class Child3 extends Parent {
Child3() {
System.out.println("Child3 생성자 호출");
}
// 오버라이딩
void viewData1() {
System.out.println("Child3 viewData1() 호출");
}
}
public class PolyMainEx05 {
public static void main(String[] args) {
// 1.
Child1 c1 = new Child1();
Child2 c2 = new Child2();
Child3 c3 = new Child3();
c1.viewData1();
c2.viewData1();
c3.viewData1();
// 2. 디형성 이용해서 호출
Parent p = new Child1();
p.viewData1();
p = new Child2();
p.viewData1();
p = new Child3();
p.viewData1();
// 3. 배열
Parent[] arr = new Parent[3];
arr[0] = new Child1();
arr[1] = new Child2();
arr[2] = new Child3();
arr[0].viewData1();
arr[1].viewData1();
arr[2].viewData1();
}
}
is-a
관계와 has-a
관계상속(Inheritance): "is-a" 관계
is-a
관계로 설명된다.Cat is an Animal
(고양이는 동물이다)포함(Composition): "has-a" 관계
Car has an Engine
(자동차는 엔진을 포함한다)class Engine {
void start() {
System.out.println("Engine start");
}
}
class Car {
// Car 클래스는 Engine 객체를 멤버로 포함함
private Engine engine = new Engine();
void startCar() {
engine.start(); // 포함된 Engine 객체의 메서드 호출
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.startCar(); // Engine의 start() 메서드 호출
}
}
Car
는 다양한 종류의 Engine
을 가질 수 있으며, 새로운 Engine
을 추가할 때 상속보다는 포함 관계가 더 적합할 수 있다.
- 상속(Inheritance)는 코드의 재사용성을 높이고, 다형성을 통해 프로그램의 유연성을 제공한다. 상속을 사용할 때는 단일 상속만 가능하지만, 부모 클래스의 멤버를 자식 클래스에서 물려받아 확장할 수 있다.
- 포함(Composition)은 상속과 달리 클래스 간의 소유 관계를 나타내며, 한 클래스가 다른 클래스를 포함하는 형태로 사용된다. 이를 통해 더 유연한 설계가 가능하며, 상속보다 더 낮은 결합도를 유지할 수 있다.