[Java ] 개념노트29 상속이란 무엇인가?

[Java 개념노트 29] 상속이란 무엇인가?

안녕하세요. Java 개념노트 시리즈 스물아홉 번째 글입니다.

지난 글에서는 클래스를 폴더처럼 분류하고 다른 패키지의 클래스를 가져오는 패키지와 import에 대해 정리했습니다. 이번 글에서는 객체지향 프로그래밍의 핵심 개념 중 하나인 상속에 대해 알아보겠습니다.

상속은 자바 객체지향에서 매우 중요한 개념입니다. 부모 클래스가 가진 필드와 메서드를 자식 클래스가 물려받아 사용할 수 있게 해주며, 코드 재사용과 확장에 큰 도움을 줍니다.


1. 상속이란?

상속은 부모 클래스가 가진 필드와 메서드를 자식 클래스가 물려받는 기능입니다.

현실에서도 부모의 특징을 자식이 물려받는다고 표현합니다. 자바에서도 비슷하게, 공통 기능을 부모 클래스에 만들고 자식 클래스가 그 기능을 이어받아 사용할 수 있습니다.

쉽게 말하면
상속은 공통 기능을 부모 클래스에 만들어두고, 자식 클래스가 그 기능을 물려받아 사용하는 객체지향 문법입니다.

2. 상속을 사용하는 이유

상속을 사용하면 공통 코드를 여러 클래스에 반복해서 작성하지 않아도 됩니다.

예를 들어 강아지, 고양이, 새 클래스가 있다고 생각해보겠습니다. 이들은 모두 이름을 가지고 있고, 먹는 동작을 할 수 있습니다. 이 공통 기능을 각각의 클래스에 모두 작성하면 코드가 반복됩니다.

class Dog {
    String name;

    void eat() {
        System.out.println(name + "이 먹습니다.");
    }
}

class Cat {
    String name;

    void eat() {
        System.out.println(name + "이 먹습니다.");
    }
}

위 코드에서는 name 필드와 eat() 메서드가 반복됩니다. 이런 공통 부분을 부모 클래스에 모아두면 코드 중복을 줄일 수 있습니다.

3. 부모 클래스와 자식 클래스

상속에서는 기능을 물려주는 클래스를 부모 클래스라고 하고, 기능을 물려받는 클래스를 자식 클래스라고 합니다.

구분 의미 다른 표현
부모 클래스 필드와 메서드를 물려주는 클래스 상위 클래스, 슈퍼 클래스
자식 클래스 부모 클래스의 기능을 물려받는 클래스 하위 클래스, 서브 클래스

영어 표현으로는 부모 클래스를 super class, 자식 클래스를 sub class라고도 합니다.

4. extends 키워드

자바에서 상속을 사용할 때는 extends 키워드를 사용합니다.

class 자식클래스 extends 부모클래스 {
    자식 클래스 내용
}

예를 들어 Dog 클래스가 Animal 클래스를 상속받는 코드는 다음과 같습니다.

class Animal {
}

class Dog extends Animal {
}

이 코드는 DogAnimal을 상속받는다는 뜻입니다. 즉, DogAnimal의 자식 클래스입니다.

5. 상속 기본 예제

이번에는 실제 필드와 메서드를 가진 예제를 보겠습니다.

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();

        dog.name = "초코";
        dog.eat();
        dog.bark();
    }
}

class Animal {
    String name;

    void eat() {
        System.out.println(name + "이 먹습니다.");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println(name + "이 짖습니다.");
    }
}

실행 결과는 다음과 같습니다.

초코이 먹습니다.
초코이 짖습니다.

Dog 클래스에는 name 필드와 eat() 메서드가 직접 작성되어 있지 않습니다. 하지만 Animal 클래스를 상속받았기 때문에 사용할 수 있습니다.

핵심 포인트
자식 클래스는 부모 클래스의 필드와 메서드를 물려받아 사용할 수 있습니다.

6. 상속 관계를 그림처럼 이해하기

상속 관계를 구조로 보면 다음과 같습니다.

Animal
 ├─ name
 └─ eat()

   ↑ 상속

Dog
 └─ bark()

Dog는 자기 자신의 기능인 bark()를 가지고 있고, 부모인 Animal로부터 nameeat()을 물려받습니다.

7. 상속으로 코드 중복 줄이기

강아지와 고양이가 모두 동물의 공통 기능을 가진다고 생각해보겠습니다. 공통 기능은 Animal 클래스에 두고, 각각의 특수 기능은 자식 클래스에 작성할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "초코";
        dog.eat();
        dog.bark();

        Cat cat = new Cat();
        cat.name = "나비";
        cat.eat();
        cat.meow();
    }
}

class Animal {
    String name;

    void eat() {
        System.out.println(name + "이 먹습니다.");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println(name + "이 짖습니다.");
    }
}

class Cat extends Animal {
    void meow() {
        System.out.println(name + "가 야옹합니다.");
    }
}

실행 결과는 다음과 같습니다.

초코이 먹습니다.
초코이 짖습니다.
나비이 먹습니다.
나비가 야옹합니다.

DogCat은 모두 Animalname 필드와 eat() 메서드를 사용할 수 있습니다. 각자 필요한 bark(), meow()만 따로 작성하면 됩니다.

8. 자식 클래스는 부모보다 확장된 개념이다

상속에서 자식 클래스는 부모 클래스보다 더 구체적인 개념입니다.

Dog는 Animal이다.
Cat은 Animal이다.

이처럼 “자식은 부모의 한 종류이다”라고 말할 수 있을 때 상속 관계가 자연스럽습니다. 이 관계를 흔히 is-a 관계라고 합니다.

상속 관계 자연스러운 문장 적절성
Dog extends Animal 강아지는 동물이다. 적절함
Car extends Vehicle 자동차는 탈것이다. 적절함
Keyboard extends Computer 키보드는 컴퓨터이다. 부자연스러움

주의
상속은 단순히 코드를 재사용하고 싶다고 무조건 사용하는 것이 아닙니다. 자식이 부모의 한 종류라고 말할 수 있을 때 사용하는 것이 자연스럽습니다.

9. 상속받은 메서드 사용하기

자식 객체는 부모 클래스의 메서드를 자신의 메서드처럼 사용할 수 있습니다.

class Vehicle {
    void move() {
        System.out.println("이동합니다.");
    }
}

class Car extends Vehicle {
    void drive() {
        System.out.println("자동차가 달립니다.");
    }
}

사용 예시는 다음과 같습니다.

Car car = new Car();

car.move();
car.drive();

move()Vehicle 클래스에 있는 메서드이지만, Car 객체에서도 사용할 수 있습니다.

10. 자식 클래스에 기능 추가하기

자식 클래스는 부모에게 물려받은 기능뿐만 아니라 자기만의 필드와 메서드를 추가할 수 있습니다.

class Animal {
    String name;

    void eat() {
        System.out.println(name + "이 먹습니다.");
    }
}

class Dog extends Animal {
    String breed;

    void bark() {
        System.out.println(name + "이 짖습니다.");
    }
}

Dog 클래스는 부모인 Animalname, eat()을 물려받고, 자기만의 breed 필드와 bark() 메서드를 추가했습니다.

11. private 멤버는 직접 접근할 수 없다

자식 클래스가 부모 클래스를 상속받더라도 부모의 private 필드나 메서드에는 직접 접근할 수 없습니다.

class Animal {
    private String name;
}

class Dog extends Animal {
    void printName() {
        System.out.println(name); // 오류 발생
    }
}

nameAnimal 클래스의 private 필드입니다. 따라서 자식 클래스인 Dog에서도 직접 접근할 수 없습니다.

이럴 때는 부모 클래스에서 public 또는 protected 메서드를 제공해서 접근하게 만들 수 있습니다.

class Animal {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

class Dog extends Animal {
    void printName() {
        System.out.println(getName());
    }
}

12. protected와 상속

상속 관계에서 부모 클래스의 멤버를 자식 클래스에서 사용할 수 있게 하고 싶을 때 protected를 사용할 수 있습니다.

class Animal {
    protected String name;

    protected void eat() {
        System.out.println(name + "이 먹습니다.");
    }
}

class Dog extends Animal {
    void printInfo() {
        name = "초코";
        eat();
    }
}

nameeat()protected이므로 자식 클래스에서 접근할 수 있습니다.

다만 모든 것을 protected로 여는 것은 좋지 않습니다. 외부에 공개할 필요가 없는 데이터는 private으로 숨기고, 필요한 메서드를 통해 접근하게 만드는 것이 더 안전한 경우가 많습니다.

13. 생성자는 상속되지 않는다

부모 클래스의 필드와 메서드는 상속될 수 있지만, 생성자는 상속되지 않습니다.

class Animal {
    Animal() {
        System.out.println("Animal 생성자");
    }
}

class Dog extends Animal {
    Dog() {
        System.out.println("Dog 생성자");
    }
}

객체를 생성해보겠습니다.

Dog dog = new Dog();

실행 결과는 다음과 같습니다.

Animal 생성자
Dog 생성자

부모 생성자가 상속되는 것은 아니지만, 자식 객체를 만들 때 부모 객체 부분이 먼저 초기화되어야 하므로 부모 생성자가 먼저 호출됩니다. 이 부분은 나중에 super 키워드를 배울 때 더 자세히 정리하겠습니다.

14. 자바는 클래스 다중 상속을 지원하지 않는다

자바 클래스는 한 번에 하나의 클래스만 상속받을 수 있습니다. 즉, 부모 클래스는 하나만 지정할 수 있습니다.

class Dog extends Animal, Pet {
    // 오류 발생
}

위 코드는 오류가 발생합니다. 자바에서는 클래스가 여러 부모 클래스를 동시에 상속받을 수 없습니다.

올바른 형태는 다음과 같습니다.

class Dog extends Animal {
}

기억하기
자바 클래스는 하나의 부모 클래스만 상속받을 수 있습니다. 다중 상속 문제는 나중에 인터페이스를 배울 때 다시 연결됩니다.

15. 상속 계층 구조

자바에서는 상속이 여러 단계로 이어질 수 있습니다.

class Animal {
    void breathe() {
        System.out.println("숨을 쉽니다.");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("짖습니다.");
    }
}

class Puppy extends Dog {
    void play() {
        System.out.println("놉니다.");
    }
}

PuppyDog를 상속받고, DogAnimal을 상속받습니다. 따라서 PuppyDog의 기능과 Animal의 기능을 모두 사용할 수 있습니다.

Puppy puppy = new Puppy();

puppy.breathe();
puppy.bark();
puppy.play();

16. Object 클래스

자바의 모든 클래스는 결국 Object 클래스를 상속받습니다. 클래스가 아무것도 상속받지 않는 것처럼 보여도 내부적으로는 Object를 상속받습니다.

class Member {
}

위 클래스는 명시적으로 아무 클래스도 상속받지 않았습니다. 하지만 자바에서는 다음과 비슷하게 볼 수 있습니다.

class Member extends Object {
}

그래서 모든 객체는 toString(), equals() 같은 Object 클래스의 기본 메서드를 가질 수 있습니다. 이 부분은 이후 기본 API와 객체 비교를 배울 때 다시 정리하겠습니다.

17. 상속과 오버라이딩 미리 보기

상속을 사용하면 부모 클래스의 메서드를 자식 클래스에서 다시 정의할 수 있습니다. 이것을 메서드 오버라이딩이라고 합니다.

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

class Dog extends Animal {
    void sound() {
        System.out.println("멍멍");
    }
}

Dog 클래스는 부모의 sound() 메서드를 자신에게 맞게 다시 정의했습니다. 오버라이딩은 다음 글에서 자세히 다루겠습니다.

18. 상속을 사용할 때 주의할 점

상속은 편리하지만 무조건 사용하는 것이 좋은 것은 아닙니다. 상속 관계가 부자연스러우면 코드 구조가 오히려 복잡해질 수 있습니다.

  • 자식이 부모의 한 종류라고 말할 수 있는지 확인해야 합니다.
  • 단순 코드 재사용만을 위해 상속을 남발하면 안 됩니다.
  • 부모 클래스가 바뀌면 자식 클래스에도 영향을 줄 수 있습니다.
  • private 멤버는 자식 클래스에서 직접 접근할 수 없습니다.
  • 자바 클래스는 하나의 부모 클래스만 상속받을 수 있습니다.

19. 상속 전체 예제

이번에는 상속을 사용해서 회원 등급을 표현하는 예제를 보겠습니다.

public class Main {
    public static void main(String[] args) {
        Member member = new Member("일반회원");
        VipMember vipMember = new VipMember("VIP회원", 10);

        member.printInfo();
        vipMember.printInfo();
        vipMember.printBenefit();
    }
}

class Member {
    protected String name;

    Member(String name) {
        this.name = name;
    }

    void printInfo() {
        System.out.println("회원 이름: " + name);
    }
}

class VipMember extends Member {
    int discountRate;

    VipMember(String name, int discountRate) {
        super(name);
        this.discountRate = discountRate;
    }

    void printBenefit() {
        System.out.println(name + "의 할인율: " + discountRate + "%");
    }
}

실행 결과는 다음과 같습니다.

회원 이름: 일반회원
회원 이름: VIP회원
VIP회원의 할인율: 10%

VipMemberMember를 상속받아 name 필드와 printInfo() 메서드를 사용할 수 있습니다. 그리고 VIP 회원만의 discountRate 필드와 printBenefit() 메서드를 추가했습니다.

20. 상속 사용 시 자주 하는 실수

상속을 처음 배울 때는 아래와 같은 실수를 자주 합니다.

실수 문제점 해결 방법
단순 코드 재사용 때문에 무조건 상속 사용 관계가 부자연스러워짐 is-a 관계인지 먼저 확인
private 필드를 자식 클래스에서 직접 접근 컴파일 오류 발생 getter/setter 또는 protected 사용 고려
부모 생성자가 상속된다고 생각 생성자 호출 흐름을 오해함 생성자는 상속되지 않지만 부모 생성자가 먼저 실행됨
여러 클래스를 동시에 extends 자바 클래스 다중 상속 불가 클래스 상속은 하나만 가능
부모 변경 영향 무시 자식 클래스 동작에도 영향 가능 상속 구조를 신중하게 설계

21. 직접 연습해보기

아래 코드를 직접 작성하고 실행해보세요.

public class Main {
    public static void main(String[] args) {
        Product product = new Product("일반 상품", 10000);
        DiscountProduct discountProduct = new DiscountProduct("할인 상품", 20000, 3000);

        product.printInfo();
        discountProduct.printInfo();
        discountProduct.printDiscountInfo();
    }
}

class Product {
    protected String name;
    protected int price;

    Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    void printInfo() {
        System.out.println("상품명: " + name + ", 가격: " + price);
    }
}

class DiscountProduct extends Product {
    int discountAmount;

    DiscountProduct(String name, int price, int discountAmount) {
        super(name, price);
        this.discountAmount = discountAmount;
    }

    void printDiscountInfo() {
        System.out.println("할인 후 가격: " + (price - discountAmount));
    }
}

실행 결과는 다음과 같습니다.

상품명: 일반 상품, 가격: 10000
상품명: 할인 상품, 가격: 20000
할인 후 가격: 17000

22. 이번 글 정리

이번 글에서는 객체지향의 핵심 개념 중 하나인 상속에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.

  • 상속은 부모 클래스의 필드와 메서드를 자식 클래스가 물려받는 기능이다.
  • 자바에서 상속은 extends 키워드를 사용한다.
  • 기능을 물려주는 클래스를 부모 클래스라고 한다.
  • 기능을 물려받는 클래스를 자식 클래스라고 한다.
  • 상속을 사용하면 공통 코드를 줄이고 기능을 확장할 수 있다.
  • 자식 클래스는 부모 클래스의 기능을 사용하면서 자기만의 기능을 추가할 수 있다.
  • 상속은 자식이 부모의 한 종류라고 말할 수 있을 때 자연스럽다.
  • 부모의 private 멤버는 자식 클래스에서 직접 접근할 수 없다.
  • 생성자는 상속되지 않지만 자식 객체 생성 시 부모 생성자가 먼저 실행된다.
  • 자바 클래스는 하나의 부모 클래스만 상속받을 수 있다.
  • 모든 클래스는 결국 Object 클래스를 상속받는다.

한 줄 요약
상속은 부모 클래스의 공통 기능을 자식 클래스가 물려받아 코드 중복을 줄이고 기능을 확장할 수 있게 해주는 객체지향 문법입니다.

다음 글 예고
다음 글에서는 [Java 개념노트 30] 메서드 오버라이딩 이해하기라는 주제로 부모 클래스의 메서드를 자식 클래스에서 다시 정의하는 방법을 정리해보겠습니다.

GWDEVELBlog Java 개념노트 시리즈