상속이란?
상속에 대해 기본을 공부해 보자! 내가 스프링 부트로 프로젝트를 진행했을 때 여러 인터페이스들을 상속받아 클래스를 만들었다.
대충은 알고 사용을 했지만, 대충 아는 건 아무 소용이 없다 생각하기 때문에 모든 기본기를 다시 공부하고 있으니, 한 번 더 정확하게 상속에 대해 공부해 보는 시간을 가져보자!
상속은 객체 지향 프로그래밍의 핵심 요소 중 하나로, 기존 클래스의 필드와 메서드를 새로운 클래스에서 재사용하게 해준다. 이름 그대로 기존 클래스의 속성과 기능을 그대로 물려받는 것이다. 상속을 사용하려면 extends 키워드를 사용하면 된다. 그리고 extends 대상은 하나만 선택할 수 있다,
용어 정리
- 부모 클래스 (슈퍼 클래스) : 상속을 통해 자신의 필드와 메서드를 다른 클래스에 제공하는 클래스
- 자식 클래스 (서브 클래스) : 부모 클래스로부터 필드와 메서드를 상속받는 클래스
package extends1.ex2;
public class Car {
public void move(){
System.out.println("차를 이동합니다");
}
}
------------------------------------------------------------
package extends1.ex2;
import extends1.ex2.ElectricCar;
import extends1.ex2.GasCar;
public class CarMain {
public static void main(String[] args) {
ElectricCar electricCar = new ElectricCar();
electricCar.move(); // Car 메서드
electricCar.charge();
GasCar gasCar = new GasCar();
gasCar.move(); // Car 메서드
gasCar.fillUp();
}
전기차와 가솔린차가 Car 를 상속받은 덕분에 electricCar.move() , gasCar.move()를 사용할 수 있다.

상속과 메모리 구조
ElectricCar electricCar = new ElectricCar();

new ElectricCar() 를 호출하면 ElectricCar뿐만 아니라 상속 관계에 있는 Car까지 함께 포함해서 인스턴 스르르 생성한다. 참조값은 x001로 하나이지만 실제로 그 안에서는 Car , ElectricCar라는 두 가지 클래스 정보가 공존하는 것이다. 상속이라고 해서 단순하게 부모의 필드와 메서드만 물려받는 게 아니다. 상속 관계를 사용하면 부모 클래스도 함께 포함해서 생성된다. 외부에서 볼 때는 하나의 인스턴스를 생성하는 것 같지만 내부에서는 부모와 자식이 모두 생성되고 공간도 구분된다.
이때, 만약 electricCar.charge() 메서드를 호출하면 먼저 호출하는 변수의 타입을 기준으로 선택한다. electricCar 변수의 타입이 ElectricCar이므로 인스턴스 내부에 같은 타입인 ElectricCar를 통해서 charge()를 호출한다.
또, electriCar.move() 메서드를 호출하면 ElectricCar로 먼저가서 선택한다. 그런데, ElectricCar에는 move() 메서드가 없다. 상속 관계에서는 자식 타입에 해당 기능이 없으면 부모 타입으로 올라가서 찾는다.
이 경우 부모인 Car로 올라가서 move()를 찾는다. 부모인 Car의 move() 메서드를 호출한다 정리 :
- 상속 관계의 객체를 생성하면 그 내부에는 부모와 자식이 모두 생성.
- 상속 관계의 객체를 호출할 때, 대상 타입을 정해야 한다. 이때 호출자의 타입을 통해 대상 타입을 찾는다.
- 현재 타입에서 기능을 찾지 못하면 상위 부모 타입으로 기능을 찾아서 실행. 기능을 찾지 못하면 컴파일 오류 발생.
상속과 메서드 오버라이딩
package extends1.ex3;
public class ElectricCar extends Car {
@Override
public void move(){
System.out.println("전기차를 빠르게 이동합니다."); //Car의 move 메서드 재정의
}
public void charge(){
System.out.println("충전합니다");
}
}
부모에게 상속받은 기능을 자식이 재정의 하는 것을 메서드 오버라이딩(Overriding)이라 한다.
오버로딩(Overlodaing)과 오버라이딩(Overriding)
- 메서드 오버로딩 : 메서드 이름이 같고 매개변수가 다른 메서드를 여러 개 정의. 오버로딩은 말 그대로 ‘과하게 담다’ 이므로 과하게 즉, ‘같은 메서드를 여러 개(과하게) 정의하였다’라고 이해하자.
- 메서드 오버라이딩 : 메서드 오버라이딩은 하위 클래스에서 상위 클래서의 메서드를 재정의하는 과정을 의미. 오버라이딩 또한 말 그대로, ‘위에 넘어서 타다’ 이므로 즉, ‘기존 기능을 새로운 기능으로 덮어썼다’라고 이해하자.
상속과 접근 제어

본인 타입에 없으면 부모 타입에서 기능을 찾는데, 이때 접근 제어자가 영향을 준다. 왜냐하면 객체 내부에서는 자식과 부모가 구분되어 있기 때문이다. 결국 자식 타입에서 부모 타입의 기능을 호출할 때, 부모 입장에서 보면 외부에서 호출한 것과 같다.
접근 제어자를 둔 것에 대해서는, “네가 아무리 자식이라도, 이건 우리 부모만의 것이야 접근하지 마!”라고 생각하자.
super -부모 참조
만약 부모와 자식 클래스의 필드명과 메서드가 같을 시, 부모 클래스의 것을 호출하고 싶다면 super 키워드를 사용하면 된다.
super -생성자
상속 관계의 인스턴스를 생성하면 결국 메모리 내부에는 자식과 부모 클래스가 각각 다 만들어진다. `Child` 를 만들면
부모인 `Parent` 까지 함께 만들어지는 것이다. 따라서 각각의 생성자도 모두 호출되어야 한다. **상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야 한다.(규칙)**
상속 관계에서 부모의 생성자를 호출할 때는 super(...)를 사용하면 된다.
public class ClassA {
public ClassA() {
System.out.println("ClassA 생성자");
}
}
--------------------------------------------------------------------------------
public class ClassB extends ClassA {
public ClassB(int a) {
super(); //기본 생성자 생략 가능
System.out.println("ClassB 생성자 a = " + a);
}
public ClassB(int a, int b) {
super(); //기본 생성자 출력 가능
System.out.println("Class B 생성자 a = " + a + " b = " + b);
}
}
--------------------------------------------------------------------------------
public class ClassC extends ClassB {
public ClassC(){
super(10, 20);
System.out.println("ClassC 생성자");
}

ClassC를 호출했기 때문에 ClassC부터 실행될 거 같지만 , 우선 부모클래스까지 차근차근 올라간 뒤에 순서대로 실행값이 나온다.
상속 관계의 생성자 호출은 결과적으로 부모에서 자식 순서로 실행된다. 따라서 부모의 데이터를 먼저 초기화하고 그다음에 자식의 데이터를 초기화한다. 상속 관계에서 자식 클래스의 생성자 첫 줄에 반드시 super(...)를 호출해야 한다. 단 기본 생성자, ( super() ) *매개 변수가 없는 인 경우 생략할 수 있다.
상속, 구현, 의존성 주입 뭐가 다를까?
상속(extends)에 대해 공부해 보았는데, 내가 1차 프로젝트 때에는 상속보다는 구현(implements)과 특히 스프링부트를 썼기에 의존성 주입 (@Autowired)을 많이 썼었다. 어떠한 다른 클래스의 기능을 받는다는 개념은 비슷한 거 같지만 아예 다른 개념들이라고 생각하여 좀 더 자세히 정리해 보기로 했다.

예시 코드 :
상속 :
Class Cat extends Animal(){
//”고양이는 동물이다.”
}
구현 :
Class Bird implements Flyable(){
//”새는 날 수 있다.”
}
의존성 주입 :
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
//유저 컨트롤러는 유저 서비스를 받아서 사용한다.
느낀 점 :
이 세 가지의 개념 모두 데이터를 용이하게 괸라할 수 있단 장점이 있다. 이 세가지의 기능을 내가 필요로 하는 상황에 맞게 자유자재로 쓸 수 있으려면, 개발을 할 때 항상 “ 이것을 왜 쓰지?”, “좀 더 나은 방법이 없을까?”라는 의문을 나에게 던져가며 주체적으로 생각하며 객체지향적인 프로그래밍을 하는 습관을 길러야겠다.
(배고픔을 주체할 수 없다 ^^ ㅎ)
'Language > ☕ Java' 카테고리의 다른 글
[Java] 다형성 2 (0) | 2024.08.14 |
---|---|
[Java] 다형성이란? (2) | 2024.08.13 |
[Java] 반복문(for) (2) | 2024.08.08 |
[Java] 메모리 구조 & static (2) | 2024.08.07 |
[Java] 접근 제어자 (0) | 2024.08.07 |