← Java 강의 목록으로
🧱
객체지향
객체지향 · 선수: 이전 단원

08. 상속

상속(inheritance) 은 기존 클래스의 필드·메서드를 **물려받아** 새로운 클래스를 정의하는 OOP 메커니즘입니다. 부모(super) 가 정해 놓은 공통 동작을 자식(sub) 에서 그대로 쓰거나 일부만 재정의(`@Override`) 할 수 있어 코드 중복을 줄여 줍니다.

JavaOOP객체지향상속
소요 시간
약 1~1.5시간
난이도
📊 초급-중급
선수 조건
🎯 이전 단원 또는 동등 지식
결과물
상속(inheritance) 은 기존 클래스의 필드·메서드를 **물려받아** 새로운 클래스를 정의하는 OOP 메커니즘입니다. 부모(super) 가 정해 놓은 공통 동작을 자식(sub) 에서 그대로 쓰거나 일부만 재정의(`@Override`) 할 수 있어 코드 중복을 줄여 줍니다.

이 강의에서 배우는 것

  • 1`extends` 로 클래스를 상속한다
  • 2`super(...)` 로 부모 생성자를 호출한다
  • 3`@Override` 어노테이션의 역할을 안다
  • 4`final` 클래스/메서드의 의미를 안다
  • 5`protected` 가 자식에게만 열린 접근 제한이라는 점을 안다

소개

상속(inheritance) 은 기존 클래스의 필드·메서드를 **물려받아** 새로운 클래스를 정의하는 OOP 메커니즘입니다. 부모(super) 가 정해 놓은 공통 동작을 자식(sub) 에서 그대로 쓰거나 일부만 재정의(`@Override`) 할 수 있어 코드 중복을 줄여 줍니다.

핵심 개념

1) `extends` 와 부모-자식 관계

java
class Animal {
    String name;
    Animal(String name) { this.name = name; }
    void speak() { System.out.println(name + ": ..."); }
}

class Dog extends Animal {
    Dog(String name) { super(name); }
    @Override
    void speak() { System.out.println(name + ": 멍멍!"); }
}

Java 는 **단일 상속(single inheritance)** 만 허용합니다. 한 클래스는 최대 한 부모만 가질 수 있습니다.

2) `super` 키워드

  • 생성자에서 `super(...)` 는 부모 생성자를 호출합니다 (반드시 첫 줄!)
  • 메서드 안에서 `super.method()` 는 오버라이딩된 부모 메서드를 호출합니다

3) `@Override`

오버라이딩하려는 메서드가 실제로 부모에 존재함을 컴파일러에게 알리고 검증받는 어노테이션입니다. **항상 붙이는 것을 권장**합니다.

4) `final`

  • `final class` : 더 이상 상속 불가
  • `final` 메서드 : 자식에서 오버라이딩 불가
  • `final` 필드 : 한 번 초기화 후 변경 불가

5) `protected`

`protected` 멤버는 같은 패키지 + 자식 클래스에서 접근 가능합니다. `private` 보다 넓고 `public` 보다 좁습니다.

핵심 예제

예제 1 — `Animal.java` + `Dog.java` : 가장 기본적인 상속

`Animal.java`:

java
public class Animal {
    protected String name;

    public Animal(String name) { this.name = name; }

    public void speak() {
        System.out.println(name + ": (기본 울음)");
    }
}

`Dog.java`:

java
public class Dog extends Animal {
    public Dog(String name) { super(name); }

    @Override
    public void speak() {
        System.out.println(name + ": 멍멍!");
    }

    public static void main(String[] args) {
        Animal a = new Animal("동물");
        Dog d = new Dog("바둑이");
        a.speak();
        d.speak();
    }
}

**실행 결과**

text
동물: (기본 울음)
바둑이: 멍멍!

**메모:** `Dog` 는 `Animal` 의 `name` 필드를 그대로 사용합니다 (`protected` 라 접근 가능).

예제 2 — `SuperKeyword.java` : `super` 활용

java
class Base {
    String tag() { return "[BASE]"; }
}

public class SuperKeyword extends Base {
    @Override
    String tag() {
        return super.tag() + " + " + "[SUB]";
    }

    public static void main(String[] args) {
        System.out.println(new SuperKeyword().tag());
    }
}

**실행 결과**

text
[BASE] + [SUB]

**메모:** 자식이 부모 메서드를 **확장** 하고 싶을 때 `super.method()` 가 유용합니다.

예제 3 — `FinalDemo.java` : 상속·재정의 차단

java
public class FinalDemo {
    static final class Constants {
        static final double PI = 3.141592;
    }

    static class Base {
        final void fixed() { System.out.println("재정의 금지"); }
        void open() { System.out.println("Base.open"); }
    }

    static class Sub extends Base {
        @Override
        void open() { System.out.println("Sub.open"); }
        // void fixed() { ... }  // 컴파일 에러: final 메서드 오버라이딩 금지
    }

    public static void main(String[] args) {
        Sub s = new Sub();
        s.fixed();
        s.open();
        System.out.println("PI=" + Constants.PI);
    }
}

**실행 결과**

text
재정의 금지
Sub.open
PI=3.141592

**메모:** `final` 은 의도적으로 상속을 끊고 싶을 때 사용합니다.

예제 4 — `MethodOverride.java` : 다양한 자식

java
class Shape {
    double area() { return 0.0; }
}

class Square extends Shape {
    double side;
    Square(double s) { this.side = s; }
    @Override double area() { return side * side; }
}

class Circle extends Shape {
    double radius;
    Circle(double r) { this.radius = r; }
    @Override double area() { return Math.PI * radius * radius; }
}

public class MethodOverride {
    public static void main(String[] args) {
        Shape[] shapes = { new Square(3), new Circle(2) };
        for (Shape s : shapes) {
            System.out.printf("area=%.3f%n", s.area());
        }
    }
}

**실행 결과**

text
area=9.000
area=12.566

**메모:** 부모 타입 배열에 자식 객체를 담아 같은 메서드를 호출하면 각자 다른 결과 — **다형성** 의 맛보기입니다 (다음 단원).

전체 예제 코드 (src/)

src/Animal.java

java
public class Animal {
    protected String name;

    public Animal(String name) { this.name = name; }

    public void speak() {
        System.out.println(name + ": (기본 울음)");
    }
}

src/Dog.java

java
public class Dog extends Animal {
    public Dog(String name) { super(name); }

    @Override
    public void speak() {
        System.out.println(name + ": 멍멍!");
    }

    public static void main(String[] args) {
        Animal a = new Animal("동물");
        Dog d = new Dog("바둑이");
        a.speak();
        d.speak();
    }
}

src/FinalDemo.java

java
public class FinalDemo {
    static final class Constants {
        static final double PI = 3.141592;
    }

    static class Base {
        final void fixed() { System.out.println("재정의 금지"); }
        void open() { System.out.println("Base.open"); }
    }

    static class Sub extends Base {
        @Override
        void open() { System.out.println("Sub.open"); }
    }

    public static void main(String[] args) {
        Sub s = new Sub();
        s.fixed();
        s.open();
        System.out.println("PI=" + Constants.PI);
    }
}

src/MethodOverride.java

java
class Shape {
    double area() { return 0.0; }
}

class Square extends Shape {
    double side;
    Square(double s) { this.side = s; }
    @Override double area() { return side * side; }
}

class Circle extends Shape {
    double radius;
    Circle(double r) { this.radius = r; }
    @Override double area() { return Math.PI * radius * radius; }
}

public class MethodOverride {
    public static void main(String[] args) {
        Shape[] shapes = { new Square(3), new Circle(2) };
        for (Shape s : shapes) {
            System.out.printf("area=%.3f%n", s.area());
        }
    }
}

src/SuperKeyword.java

java
class Base {
    String tag() { return "[BASE]"; }
}

public class SuperKeyword extends Base {
    @Override
    String tag() {
        return super.tag() + " + " + "[SUB]";
    }

    public static void main(String[] args) {
        System.out.println(new SuperKeyword().tag());
    }
}

자주 하는 실수

  1. 부모 생성자 호출이 생성자 첫 줄이어야 한다는 규칙 위반
  2. `@Override` 빼먹고 시그니처가 살짝 달라서 오버로딩이 됨 (조용한 버그)
  3. `private` 메서드를 오버라이딩한다고 착각 (사실은 새 메서드)
  4. 무리한 상속 (is-a 관계가 아닌데 `extends`)
  5. 깊은 상속 계층 → 가독성 저하; 보통 **2~3 단계** 이내가 권장

정리

  • 상속은 코드 재사용 + 다형성의 기반
  • `@Override` 는 무조건 붙이는 게 안전
  • `final` 로 의도적 차단 가능
  • 상속보다 **조합(composition)** 이 종종 더 유연한 대안

과제

# 과제 - 08. 상속

## 문제 1 — `Employee` 와 `Manager`

  • 파일명: `Homework01.java`
  • 핵심 개념: `extends`, `super`, 메서드 오버라이딩

요구사항

  • 부모 `Employee(name, salary)` + `report()` 가 `홍길동 월급 3000000` 형식 출력.
  • 자식 `Manager(name, salary, bonus)` 는 `report()` 를 오버라이딩해 보너스 포함 총액을 출력.
  • main 에서 둘 다 호출.

예상 출력

text
홍길동 월급 3000000
김부장 월급 5000000 + 보너스 1000000 = 6000000

## 문제 2 — `Shape` 계층

  • 파일명: `Homework02.java`
  • 핵심 개념: 공통 부모 + 오버라이딩 + `@Override`

요구사항

  • 부모 `Shape` 에 `double area()` 메서드(반환 0.0).
  • 자식 `Square`, `Triangle` 가 각자 `area()` 오버라이딩.
  • `Shape[]` 배열에 담아 면적 출력.

예상 출력

text
Square: 9.0
Triangle: 6.0

## 정답 확인 직접 풀어 본 후 [`answer/`](./answer/) 폴더의 정답과 비교해 보세요.

정답 코드 (homework/answer/)

answer/Homework01.java

java
/** Employee 와 Manager 상속. */
public class Homework01 {
    public static void main(String[] args) {
        Employee e = new Employee("홍길동", 3_000_000);
        Manager m = new Manager("김부장", 5_000_000, 1_000_000);
        e.report();
        m.report();
    }
}

class Employee {
    protected String name;
    protected long salary;

    Employee(String name, long salary) {
        this.name = name;
        this.salary = salary;
    }

    void report() {
        System.out.println(name + " 월급 " + salary);
    }
}

class Manager extends Employee {
    private long bonus;

    Manager(String name, long salary, long bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    @Override
    void report() {
        System.out.println(name + " 월급 " + salary + " + 보너스 " + bonus
                + " = " + (salary + bonus));
    }
}

answer/Homework02.java

java
/** Shape 계층 면적. */
public class Homework02 {
    public static void main(String[] args) {
        ShapeHW[] shapes = { new SquareHW(3), new TriangleHW(4, 3) };
        for (ShapeHW s : shapes) {
            System.out.println(s.getClass().getSimpleName().replace("HW", "")
                    + ": " + s.area());
        }
    }
}

class ShapeHW {
    double area() { return 0.0; }
}

class SquareHW extends ShapeHW {
    double side;
    SquareHW(double s) { this.side = s; }
    @Override double area() { return side * side; }
}

class TriangleHW extends ShapeHW {
    double base, height;
    TriangleHW(double base, double height) { this.base = base; this.height = height; }
    @Override double area() { return base * height / 2.0; }
}

직접 해 보기

bash
cd 02_객체지향/08_상속/src
javac Animal.java Dog.java
java Dog

다음 단원

[09_다형성](../09_다형성/) — 업/다운캐스팅, `instanceof` 패턴 매칭, `abstract` 클래스를 배웁니다.

예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗