상속(Inheritance)

SEUNGJUN·2024년 1월 20일

상속이란?

객체 지향 프로그래밍에서 중요한 개념 중 하나로, 한 클래스가 다른 클래스의 멤버 변수와 메서드를 확장하거나 재사용하는 매커니즘이라고 한다.

상속의 특징

1. 코드 재사용성

  • 이미 구현된 클래스의 코드를 재사용하여 새로운 클래스를 작성한다.
  • 부모 클래스에서 정의된 멤버 변수와 메서드를 자식 클래스에서 그대로 사용하거나 필요에 맞게 수정하여 사용할수 있다.

2. 계층 구조

  • 클래스들 간의 계층 구조를 형성하여 객체 간의 관계를 명확하게 한다.
  • 부모 클래스와 자식 클래스의 상속 구조를 통해 객체의 관련성을 표현할수 있다.

3. 매서드 재정의

  • 자식 클래스에서 부모 클래스의 메서드를 다시 정의하여 동일한 메서드 시그니처를 가지면서 특정 동작을 변경할 수 있다.
  • 다형성을 구현할 수 있다.

상속 예제

부모 클래스

class Animal {
    String name;

    // 생성자: 객체가 생성될 때 초기화를 위해 사용됨
    Animal(String name) {
        this.name = name;
    }

    // 메서드: 동물이 소리를 내도록 하는 기본 동작 정의
    void makeSound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}
  • 부모 클래스에서는 Animal이라는 공통 특성을 가지고 있고, makeSound라는 메서드를 통해서 동물의 울음소리를 동작하도록 정의하고 있다.

자식 클래스

class Dog extends Animal {
    // 생성자: 부모 클래스의 생성자 호출하여 초기화
    Dog(String name) {
        super(name);
    }

    // 메서드 재정의(override): 부모 클래스의 메서드를 새로 정의
    @Override
    void makeSound() {
        System.out.println(name + "가 멍멍 소리를 냅니다.");
    }

    // 추가 메서드: 꼬리를 흔들도록 하는 동작 추가
    void wagTail() {
        System.out.println(name + "가 꼬리를 흔듭니다.");
    }
}
  • 자식 클래스에서는 Dog 클래스로 Animal 클래스를 상속 받고 있다. 이때 Dog(String name)에서 부모 클래스의 생성자를 호출해서 name을 초기화 하고 부모 클래스가 가지고 있는 makeSound를 Override해서 Dog 클래스에 맞게 변환 할수가 있다. wagTail과 같은 메서드를 추가 할수도 있다.

객체 사용

public class Main {
    public static void main(String[] args) {
        // Dog 클래스의 인스턴스 생성
        Dog myDog = new Dog("멍멍이");

        // 부모 클래스의 메서드 호출
        myDog.makeSound();  // 멍멍이가 멍멍 소리를 냅니다.

        // 자식 클래스의 추가 메서드 호출
        myDog.wagTail();    // 멍멍이가 꼬리를 흔듭니다.
    }
}
  • Dog 클래스의 인스턴스 myDog를 생성하고, 이름을 멍멍이로 초기화 한다. myDog.makeSound()를 호출하면 재정의가된 메서드가 실행되고, myDog.wagTail()에서는 추가된 메서드가 실행이 된다.

상속의 주의사항

1. 다중 상속의 주의

class A {}
class B extends A {}
class C extends A {}
// class D extends B, C {} // 다중 상속은 허용되지 않음
  • 클래스 간의 충돌이나 모호성 방지를 위해서 다중으로 상속을 하는것은 피해야 한다.

2. 매서드 재정의 주의

class Animal {
    void makeSound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

class Dog extends Animal {
    // 오버라이딩할 때 메서드 시그니처를 변경하면 오류 발생
    // void makeSound(String sound) {} // 컴파일 오류
}
  • 매서드 재정의 시 매서드 시그니처(이름, 목록, 리턴 타입)를 변경하면 컴파일 오류가 발생할수 있다.

3. 매서드 접근 제어자

class Animal {
    protected void eat() {
        System.out.println("동물이 먹습니다.");
    }
}

class Dog extends Animal {
    // 부모 클래스의 protected 메서드를 'private'로 변경 불가 
	// 예외로 'public'은 가능하다.
    public void eat() {
        System.out.println("강아지가 먹습니다.");
    }
}
  • 매서드 접근 제어자를 확장할수 없다. eat 메서드를 private로 변경하려면 컴파일 오류가 발생한다.

4. 의존성 주의

class Engine {}

// 의존성이 강한 상속
class CarWithInheritance extends Engine {}

// 의존성을 줄인 구성
class CarWithComposition {
    private Engine engine;

    CarWithComposition(Engine engine) {
        this.engine = engine;
    }
}
  • 상속은 의존성이 강하게 될 수 있으므로, 상속 대신 구성을 사용하여 의존성을 줄이는 것이 바람직 하다.

5. Final 클래스와 메서드

final class FinalClass {}

// Final 클래스는 상속할 수 없음
// class SubClass extends FinalClass {} // 컴파일 오류
  • final 키워드를 사용하여 클래스를 선언하면 상속을 할수가 없다. 또한 메서드 final로 선언되면 오버라이딩이 불가능 하다.
profile
RECORD DEVELOPER

0개의 댓글