[JAVA] 상속
상속이란?
- 상속(Inheritance)은 부모 클래스의 필드와 메서드를 자식 클래스가 물려받아 사용하는 기능이다.
- 중복 코드를 줄이고, 공통된 기능을 재사용할 수 있음
- extends키워드를 사용하여 상속
- 자식 클래스는 부모의 기능을 사용할 수 있지만, 부모는 자식의 기능을 모름
- 자바는 단일 상속만 허용함 (하나의 부모만 상속 가능)
상속을 왜 쓰는지?
전기차와 가솔린차 클래스가 있다고 해보자. 각각 move() 라는 공통 기능이 있음에도 따로 구현되어 있다면, 중복을 줄이고 구조를 더 깔끔하게 만들 방법이 필요함. 바로 이럴 때 상속이 등장한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ElectricCar {
    public void move() {
        System.out.println("차를 이동합니다.");
    }
    public void charge() {
        System.out.println("충전합니다.");
    }
}
public class GasCar {
    public void move() {
        System.out.println("차를 이동합니다.");
    }
    public void fillUp() {
        System.out.println("기름을 주유합니다.");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CarMain {
  public static void main(String[] args) {
    ElectricCar electricCar = new ElectricCar();
    electricCar.move();
    electricCar.charge();
    GasCar gasCar = new GasCar();
    gasCar.move();
    gasCar.fillUp();
  }
}
차를 이동합니다.
충전합니다.
차를 이동합니다.
기름을 주유합니다.
move() ← 코드 중복이 확인된다.
이런경우 상속을 사용하는 것이 좋다. 부모 클래스 Car 을 만들어 자식 클래스에서 상속 받으면 된다.
상속 적용
1
2
3
4
5
6
//부모 클래스
public class Car {
    public void move() {
        System.out.println("차를 이동합니다.");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//자식 클래스가 부모 클래스를 상속 받음 (extend)
public class ElectricCar extends Car {
    public void charge() {
        System.out.println("충전합니다.");
    }
}
//자식 클래스가 부모 클래스를 상속 받음 (extend)
public class GasCar extends Car {
    public void fillUp() {
        System.out.println("기름을 주유합니다.");
    }
}
전기차와 가솔린차가 Car 를 상속 받은 덕분에 electricCar.move() , gasCar.move() 를 사용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
ElectricCar electricCar = new ElectricCar();
electricCar.move();   // 부모의 move() 호출
electricCar.charge();
GasCar gasCar = new GasCar();
gasCar.move();        // 부모의 move() 호출
gasCar.fillUp();
차를 이동합니다.
충전합니다.
차를 이동합니다.
기름을 주유합니다.
- 부모 클래스 (Super Class): 상속을 제공하는 클래스 (Car)
- 자식 클래스 (Sub Class): 상속을 받는 클래스 (ElectricCar,GasCar)
- 자식 —> 부모 호출은 가능
- 
    부모 —> 자식 호출은 불가능 ( Car클래스는ElectricCar나GasCar를 모른다!)
자바는 다중 상속을 허용하지 않음
다중 상속을 허용하면 move() 같은 메서드가 어떤 부모의 것을 써야 할지 모호해짐 → 다이아몬드 문제
그래서 자바는 하나의 클래스만 상속 가능. 물론 부모가 또 다른 부모를 하나 가지는 것은 괜찮다.
대신 인터페이스는 여러 개 구현 가능해서, 다중 상속의 일부 장점을 인터페이스로 보완한다.
상속과 메모리 구조
1
ElectricCar electricCar = new ElectricCar();
- ElectricCar인스턴스를 생성하면, 내부적으로는 부모 클래스인- Car까지 포함되어 생성된다.
- 하나의 참조값(x001) 안에 부모(Car) + 자식(ElectricCar) 구조가 겹쳐져 존재한다.
- electricCar의 타입은- ElectricCar
- ElectricCar내부에서- charge()메서드를 찾음
- 자식 클래스에 charge()존재 → 바로 실행
- ElectricCar내부에- move()없음
- 상속 관계에 따라 부모 클래스(Car) 로 올라감
- Car에- move()존재 → 부모의 메서드 실행
자식 → 부모 순서대로 메서드 탐색
상속 시 객체 구조 : 부모 + 자식 하나의 인스턴스에 같이 생성
메서드 탐색 순서 : 자식 → 부모 → 조부모..
참조 변수 타입 : 메서드 탐색 기준이 됨 ElectricCar electricCar
호출 불가능한 경우 : 컴파일 에러
상속과 기능 추가
1
2
3
4
5
6
7
8
9
10
public class Car {
    public void move() {
        System.out.println("차를 이동합니다.");
    }
    // 공통 기능 추가
    public void openDoor() {
        System.out.println("문을 엽니다.");
    }
}
새로운 자식 클래스 추가
1
2
3
4
5
public class HydrogenCar extends Car {
    public void fillHydrogen() {
        System.out.println("수소를 충전합니다.");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ElectricCar electricCar = new ElectricCar();
electricCar.move();         // 부모 기능
electricCar.charge();       // 자식 고유 기능
electricCar.openDoor();     // 부모에 추가된 새 기능
HydrogenCar hydrogenCar = new HydrogenCar();
hydrogenCar.move();
hydrogenCar.fillHydrogen();
hydrogenCar.openDoor();
차를 이동합니다.
충전합니다.
문을 엽니다.
차를 이동합니다.
수소를 충전합니다.
문을 엽니다.
상속과 메서드 오버라이딩(Overriding)
부모에게 상속받은 메서드를 자식 클래스에서 새로운 방식으로 재정의하는 것
1
2
3
4
@Override
public void move() {
    System.out.println("전기차를 빠르게 이동합니다.");
}
- 부모 Car의move()는 “차를 이동합니다.”
- 자식 ElectricCar에서move()를 오버라이딩 → “전기차를 빠르게 이동합니다.”
부모클래스
1
2
3
4
5
6
7
8
9
public class Car {
    public void move() {
        System.out.println("차를 이동합니다.");
    }
    public void openDoor() {
        System.out.println("문을 엽니다.");
    }
}
자식클래스
1
2
3
4
5
6
7
8
9
10
public class ElectricCar extends Car {
    @Override
    public void move() {
        System.out.println("전기차를 빠르게 이동합니다.");
    }
    public void charge() {
        System.out.println("충전합니다.");
    }
}
메인
1
2
3
4
ElectricCar electricCar = new ElectricCar();
electricCar.move(); // 오버라이딩된 메서드 호출
전기차를 빠르게 이동합니다.
- electricCar는- ElectricCar타입
- ElectricCar내부에서- move()찾음 → 있음 → 그 메서드 실행
- 부모까지 올라가지 않음
@Override 애노테이션
- 상위 클래스의 메서드를 정확히 오버라이드하는지 컴파일 타임에 체크
- 실수 방지 및 코드 명확성 향상
- 안 붙여도 되지만 무조건 붙이는 습관 권장
1
2
3
4
@Override
public void move() {
    // ...
}
오버라이딩 조건
부모 메서드와 같은 메서드를 오버라이딩 할 수 있다 정도로 이해하면 충분하다.
- 메서드 이름: 부모와 같아야 함
- 매개변수: 타입, 개수, 순서 동일
- 반환 타입: 동일 or 하위 타입
- 
    접근 제어자: 더 좁으면 안 됨 상위 클래스의 메서드가 protected 로 선언되어 있으면 하위 클래스에서 이를 public 또는 protected 로 오버라이드할 수 있지만, private 또는 default 로 오버라이드 할 수 없다. 
- 예외: 더 많은 체크 예외 선언 ❌
- static,- final,- private메서드: 오버라이딩 ❌
- 생성자: 오버라이딩 불가
@Override 를 반드시 붙이자!
상속과 접근제어자
상속 관계에서 자식 클래스는 부모 클래스의 필드와 메서드에 접근 제어자에 따라 접근할 수 있다.
접근 제어자는 정보 은닉과 캡슐화의 핵심!
| 접근 제어자 | 의미 | 같은 클래스 | 같은 패키지 | 자식 클래스(다른 패키지) | 외부 | 
|---|---|---|---|---|---|
| private | 완전 차단 | ✅ | ❌ | ❌ | ❌ | 
| (default) | 패키지 전용 | ✅ | ✅ | ❌ | ❌ | 
| protected | 패키지 + 상속 허용 | ✅ | ✅ | ✅ | ❌ | 
| public | 전부 허용 | ✅ | ✅ | ✅ | ✅ | 
부모 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Parent {
    public int publicValue;
    protected int protectedValue;
    int defaultValue;
    private int privateValue;
    public void publicMethod() { ... }
    protected void protectedMethod() { ... }
    void defaultMethod() { ... }
    private void privateMethod() { ... }
    public void printParent() {
        // 모든 필드/메서드 접근 가능
    }
}
자식 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Child extends Parent {
    public void call() {
        publicValue = 1;       // 가능
        protectedValue = 1;    // 가능 (상속 관계이므로)
        // defaultValue = 1;   // ❌ 다른 패키지 접근 불가
        // privateValue = 1;   // ❌ 접근 불가
        publicMethod();        // 가능
        protectedMethod();     // 가능
        // defaultMethod();    // ❌
        // privateMethod();    // ❌
        printParent();         // public 메서드는 호출 가능
    }
}
Parent.publicMethod
Parent.protectedMethod
==Parent 메서드 안==
publicValue = 1
protectedValue = 1
defaultValue = 0
privateValue = 0
Parent.defaultMethod
Parent.privateMethod
| 필드/메서드 | 자식 클래스에서 접근 | 이유 | 
|---|---|---|
| public | ✅ 가능 | 외부 포함 모두 접근 가능 | 
| protected | ✅ 가능 | 상속 관계이므로 가능 | 
| default(패키지 전용) | ❌ 불가 | 다른 패키지라서 불가 | 
| private | ❌ 불가 | 클래스 내부에서만 사용 가능 | 
- 자식 클래스는 부모의 public,protected만 접근 가능
- default,- private는 자식이라도 다른 패키지면 접근 불가
- 부모 클래스 내부에서는 자신의 모든 멤버에 자유롭게 접근 가능
public, protected 만 접근 가능하며, 부모 클래스 내부에서는 자신의 모든 필드와 메서드에 접근 할 수 있다.
접근제어자 UML 표기법
- +: public
- #: protected
- ~: default (package-private)
- -: private
접근제어자와 메모리 구조
상속 관계에서 객체가 생성되면, 부모 클래스와 자식 클래스가 하나의 인스턴스 안에 함께 존재 하지만 메모리 내부에서는 구분 되어 있다.
자식에서 부모의 기능을 호출하는 건 부모 입장에서는 외부에서 접근하는 것과 같음
즉, 아무리 내부에 있어도
- private,- default는 부모 외부 접근 차단이므로 → 자식 클래스에서 접근 불가
- protected,- public만 접근 가능
super
부모의 필드/메서드 참조
자식 클래스에서 부모의 필드나 메서드와 이름이 같을 때, 부모 쪽 기능을 호출하고 싶을 때 사용
부모 클래스
1
2
3
4
5
6
public class Parent {
    public String value = "parent";
    public void hello() {
        System.out.println("Parent.hello");
    }
}
자식 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Child extends Parent {
    public String value = "child";
    @Override
    public void hello() {
        System.out.println("Child.hello");
    }
    public void call() {
        System.out.println("this value = " + this.value);   // child
        System.out.println("super value = " + super.value); // parent
        this.hello();   // Child.hello
        super.hello();  // Parent.hello
    }
}
메인 클래스
1
2
3
4
5
6
7
8
9
10
11
12
public class Super1Main {
	 public static void main(String[] args) {
			 Child child = new Child();
			 child.call();
	 }
}
-- 실행결과 --
this value = child
super value = parent
Child.hello
Parent.hello
super 는 부모 클래스의 필드나 메서드를 참고할 때 사용!
이름이 겹칠 경우, 자식이 우선이므로 super 로 명시적으로 부모 접근 필요!
부모 생성자 호출
자식 객체를 생성하면, 부모 객체도 함께 생성됨 따라서 자식 생성자에서 반드시 부모 생성자도 호출해야 함
생성자 규칙
- 자식 생성자의 첫 줄에서 super(...)를 호출해야 함
- 생략 시 → 컴파일러가 자동으로 super()추가
- 부모에 기본 생성자 없으면, 반드시 명시적으로 super(...)호출해야 함
1
2
3
4
5
public class ClassA {
    public ClassA() {
        System.out.println("ClassA 생성자"); //5
    }
}
1
2
3
4
5
public class ClassB extends ClassA {
    public ClassB(int a, int b) { //3
        super(); // 부모의 기본 생성자 호출 4
        System.out.println("ClassB 생성자 a=" + a + " b=" + b); //6
    }
1
2
3
4
5
6
public class ClassC extends ClassB {
    public ClassC() {
        super(10, 20); // 부모 생성자 직접 선택 2
        System.out.println("ClassC 생성자"); //7
    }
}
1
2
3
4
5
6
7
8
9
10
public class Super2Main {
	 public static void main(String[] args) {
			 ClassC classC = new ClassC(); //1
	 }
}
-- 출력 결과 --
ClassA 생성자
ClassB 생성자 a=10 b=20
ClassC 생성자
- ClassC()가 호출됨
- → 가장 먼저 super(10, 20)호출 →ClassB(int a, int b)
- → 그 안에서 다시 super()호출 →ClassA()
this(…) 와 super(…)의 관계
- 생성자에서 this(...)로 같은 클래스의 다른 생성자 호출 가능
- 하지만 결국에는 한 번은 반드시 super(...)호출해야 함
1
2
3
4
5
6
7
8
9
10
11
12
public class ClassB extends ClassA {
    public ClassB(int a) {
        this(a, 0); // 자기 생성자 호출
        System.out.println("ClassB 생성자 a=" + a);
    }
    public ClassB(int a, int b) {
        super(); // 부모 생성자 호출
        System.out.println("ClassB 생성자 a=" + a + " b=" + b);
    }
}
1
2
3
4
5
6
7
8
9
10
public class Super2Main {
	 public static void main(String[] args) {
			ClassB classB = new ClassB(100);
	 }
}
-- 출력 결과 --
ClassA 생성자
ClassB 생성자 a=100 b=0
ClassB 생성자 a=100
- this(...)는 같은 클래스의 다른 생성자 호출
- super(...)는 부모 클래스 생성자 호출
- 생성자 호출은 반드시 최상위 부모 → 자식 순서로 끝까지 호출됨
정리
- 상속은 부모 클래스의 필드와 메서드를 자식 클래스가 물려받아 사용하는 기능
- 중복 코드를 줄이고, 공통된 기능을 효율적으로 재사용할 수 있음
- 상속 구조에서는 객체 내부에 부모와 자식이 함께 생성되며, 메서드 탐색은 자식 → 부모 순서로 이루어짐
- 상속을 통해 기능을 편리하게 확장할 수 있고, 오버라이딩을 통해 자식만의 동작을 정의할 수 있음
- 자식 클래스는 부모의 public,protected멤버만 접근 가능
- super키워드를 사용하면 부모의 필드, 메서드, 생성자에 접근 가능함
상속은 자바 객체지향의 핵심 기초 개념 중 하나이며, 앞으로 배울 다형성, 추상 클래스, 인터페이스 개념의 기반이 된다.
출처: 김영한의 실전 자바 - 기본편



