상속 관계
상속은 객체 지향 프로그래밍의 핵심 요소 중 하나로, 기존 클래스의 필드와 메서드를 새로운 클래스에서 재사용하게 해준다.
기존 클래스의 속성과 기능을 그대로 물려받는 것이다. 상속을 사용하려면 extends 키워드를 사용하면 된다.
extends 대상은 하나만 선택할 수 있다.
- 부모 클래스(슈퍼 클래스) : 상속을 통해 자신의 필드와 메서드를 다른 클래스에게 제공하는 클래스
- 자식 클래스(서브 클래스) : 부모 클래스로 부터 필드와 메서드를 상속받는 클래스
public class Car {
public void move(){
System.out.println("차를 이동합니다.");
}
}
public class ElectricCar extends Car{
public void charge() {
System.out.println("충전합니다.");
}
}
public class GasCar extends Car{
public void fillUp() {
System.out.println("기름을 주유 합니다.");
}
}
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();
}
}
전기차와 가솔린차가 Car를 상속 받은 덕분에 move() 메서드를 사용할 수 있따.
상속은 부모의 기능을 자식이 물려 받는 것이다. 따라서 부모 클래스는 자식 클래스에 접근할 수 없다.
단일 상속
자바는 다중 상속을 지원하지 않는다. extend 대상은 하나만 선택할 수 있다.
예를 들어 다중 상속을 사용하게 되면 Air 의 부모의 move() 와 Car 의 부모의 move()를 호출할 때 어떤 부모의 move()를 사용해야 할지 애매한 문제가 발생한다. 이것을 다이아몬드 문제라 한다. 다중 상속을 사용하면 클래스 계층 구조가 매우 복잡해질 수 있다.
상속과 메모리 구조
new ElectricCar()를 호출하면 ElectricCar 뿐만 아니라 상속 관계에 있는 Car 까지 함께 포함해서 인스턴스를 생성한다.
참조값은 ex) x001로 하나 이지만 실제로 그 안에서는 Car, ElectricCar 라는 두가지 클래스 정보가 공존한다.
외부에서 볼때는 하나의 인스턴스를 생성하는 것 같지만 내부에서는 부모와 자식이 모두 생성되고 공간도 구분된다.
ElectricCar electricCar 에서 electricCar.charge()를 호출하게 되면 참조값을 확인해서 x001.charge()를 호출한다.
상속 관계의 경우에는 내부에 부모와 자식이 모두 존재한다. 이때 부모에게서 charge()를 찾을지 자식에게서 charge()를 찾을지는
호출하는 변수의 타입을 기준으로 선택한다. electricCar의 변수의 타입이 ElectricCar 이므로 인스턴스 내부에 같은 타입인
ElectricCar 를 통해서 charge()를 호출한다.
만약 호출했을때 자식에게서 없다면 부모에게 가서 메서드를 찾는다.
상속과 메서드 오버라이딩
부모에게서 상속받은 기능을 자식이 재정의 하는 것을 메서드 오버라이딩이라 한다.
public class Car {
public void move(){
System.out.println("차를 이동합니다.");
}
public void openDoor() {
System.out.println("문을 엽니다.");
}
}
public class ElectricCar extends Car {
@Override
public void move(){
System.out.println("전기차를 빠르게 이동합니다.");
}
public void charge() {
System.out.println("충전합니다.");
}
}
ElectricCar는 move() 메서드를 새로 만들어서 ElectricCar의 move() 를 호출하면 Car의 move()가 아니라 ElectricCar의 move()가 호출된다.
@Override
@이 붙은 부분을 애노테이션이라 한다. 프로그램이 읽을 수 있는 특별한 주석같은 것이다.
이 애노테이션은 상위 클래스의 메서드를 오버라이드 하는 것임을 나타낸다.
오버라이딩 조건을 만족 시키지 않으면 컴파일 에러를 발생시킨다.
참고로 이 기능은 필수가 아니지만 코드의 명확성을 위해 붙여주는 것이 좋다.
오버로딩과 오버라이딩
- 메서드 오버로딩 : 메서드 이름이 같고 매개변수(파라미터)가 다른 메서드를 여러개 정의하는 것을 말한다.
- 메서드 오버라이딩 : 하위 클래스에서 상위 클래스의 메서드를 재정의 하는 과정을 의미한다.
메서드 오버라이딩 조건
- 메서드 이름 : 메서드 이름이 같아야 한다.
- 메서드 매개변수(파라미터) : 매개변수(파라미터) 타입, 순서, 개수가 같아야 한다.
- 반환 타입 : 반환 타입이 같아야 한다. 단 반환 타입이 하위 클래스 타입일 수 있다.
- 접근 제어자 : 오버라이딩 메서드의 접근 제어자는 상위 클래스의 메서드보다 더 제한적이어서는 안된다. ex) 상위 클래스가 proteced로 선언되어 있으면 하위 클래스에서 public 또는 protected로 오버라이딩 가능, private, default는 불가
- static, final, private : 키워드가 붙은 메서드는 오버라이딩 될 수 없다.
- static: static은 클래스 레벨에서 작동하므로 인스턴스 레벨에서 사용하는 오버라이딩이 의미가 없다.
- final : 메서드는 재정의를 금지한다.
- private : 메서드는 해당 클래스에서만 접근 가능하기 때문에 하위 클래스에서 보이지 않는다.
- 생성자 오버라이딩 : 생성자는 오버라이딩 할 수 없다.
상속과 접근 제어
- protected : 같은 패키지 안에서 호출은 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.
public class Parent {
public int publicValue;
protected int protectedValue;
int defaultValue;
private int privateValue;
public void publicMethod() {
System.out.println("Parent.publicMethod");
}
protected void protectedMethod(){
System.out.println("Parent.protectedMethod");
}
void defaultMethod(){
System.out.println("Parent.defaultMethod");
}
private void privateMethod(){
System.out.println("Parent.privateMethod");
}
public void printParent(){
System.out.println("==Parent 메서드 안==");
System.out.println("publicValue = " + publicValue);
System.out.println("protectedValue = " + protectedValue);
System.out.println("defaultValue = " + defaultValue);
System.out.println("privateValue = " + privateValue);
defaultMethod();
privateMethod();
}
}
public class Child extends Parent {
public void call(){
publicValue = 1;
protectedValue = 1; // 상속관계 or 같은 패키지
//defaultValue = 1; // 다른패키지 접근 불가, 컴파일 오류
//privateValue 1; //접근 불가, 컴파일 오류
publicMethod();
protectedMethod();
//defaultMethod();
//privateMethod();
printParent();
}
}
자식 클래스인 Child에서 부모 클래스인 Parent에 접근을 보면
부모의 public 필드, 부모의 protected 필드에 접근 가능하다.
default 필드는 자식과 부모가 다른 패키지 이므로 접근 불가능하다.
private 필드는 모든 외부 접근을 막으므로 자식이라도 호출할 수 없다.
super - 부모 참조
부모와 자식의 필드명이 같거나 메서드가 오버라이딩 되어 있으면, 자식에서 부모의 필드나 메서드를 호출할 수 없다.
public class Parent {
public String value = "parent";
public void hello() {
System.out.println("Parent.hello");
}
}
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);
System.out.println("super value=" + super.value);
this.hello(); //this 생략 가능
super.hello();
}
}
public class Super1Main {
public static void main(String[] args) {
Child child = new Child();
child.call();
}
}
this는 자기 자신의 참조를 뜻한다. this는 생략할 수 있다.
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("ClassB 생성자 a="+ a + " b=" + b);
}
}
ClassB는 ClassA를 상속 받았다. 상속을 받으면 생성자의 첫줄에 super()를 사용해서 부모 클래스의 생성자를 호출해야 한다.
부모 클래스의 생성자가 기본 생성자인 경우에느 super() 를 생략할 수 있다.
public class ClassC extends ClassB{
public ClassC() {
super(10, 20);
System.out.println("ClassC 생성자");
}
}
ClassC는 ClassB를 상속 받았다.
ClassB에는 생성자가 2개 있다.
생성자는 하나만 호출할 수 있다. 두 생성자 중에 하나를 선택하면 된다.
ClassB에는 기본 생성자가 없다. ClassC는 부모의 기본 생성자를 호출하는 super()를 사용하거나 생략할 수 없다.
public class Super2Main {
public static void main(String[] args) {
ClassC classC = new ClassC();
}
}
실행하면 실행 결과는 ClassA -> ClassB - > ClassC 순서로 실행된다.
생성자의 실행 순서가 결과적으로 최상위 부모부터 실행되어서 하나씩 아래로 내려오는 것이다.
초기화는 최상위 부모부터 이루어진다. 왜냐하면 자식 생성자의 첫줄에서 부모의 생성자를 호출해야 하기 때문이다.
this()와 함께 사용
코드의 첫줄에 this()를 사용하더라도 반드시 한번은 super를 호출해야 한다.
Reference
'JAVA' 카테고리의 다른 글
문자열 뒤집기 - StringBuilder.reverse() (0) | 2025.06.12 |
---|---|
김영한의 실전 자바 기본편 - 8일차 (0) | 2025.05.10 |
김영한의 실전 자바 기본편 - 6일차 (0) | 2025.04.27 |
김영한의 실전 자바 기본편 - 5일차 (0) | 2025.04.20 |
김영한의 실전 자바 기본편 - 4일차 (0) | 2025.04.13 |