[Java 기본]Section 9 - 상속

우영·2024년 3월 1일

JAVA 기본편

목록 보기
9/13

상속을 사용하는 이유

package extends1.ex;

package extends1.ex1;
public class ElectricCar {
 public void move() {
 System.out.println("차를 이동합니다.");
 }
 public void charge() {
 System.out.println("충전합니다.");
 }
}
package extends1.ex1;
public class GasCar {
 public void move() {
 System.out.println("차를 이동합니다.");
 }
 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();
    }
}

  • 전기차와 가솔리차는 자동차의 좀 더 구체적인 개념. 반대로 자동차는 전기차와 가솔림차를 포함하는 추상적인 개념이다. 둘의 공통점으로 move가 있는데 이럴때 상속 관계를 사용하는 것이 효과적이다.

상속 관계

객체 지향 요소 프로그래밍의 핵심 요소 중 하나로, 기존 클래스의 필드와 메서드를 새로운 클래스에서 재사용하게 해준다. 이름 그대로 기존 클래스의 속성과 기능을 그대로 물려받는 것. 상속을 사용하려면 extends 키워드를 사용. extends 대상은 하나만 선택가능

용어정리

부모 클래스(슈퍼 클래스) : 상속을 통해 자신의 필드와 매소드를 다른 클래스에 제공하는 클래스
자식 클래스(서브 클래스) : 부모 클래스로부터 필드와 메서드를 상속받는 클래스

상속 관계를 써보기

  • 기존 코드를 유지하기 위해 ex2 패키지를 새로 만들자
package extends1.ex2;
public class Car {
 public void move() {
 System.out.println("차를 이동합니다.");
 }
}
  • car는 부모 클래스가 된다. 공통 기능인 move가 들어있다.

package extends1.ex2;
public class ElectricCar extends Car {
 public void charge() {
 System.out.println("충전합니다.");
 }
}
  • 전기차는 extends Car를 사용해서 부모 클래스인 Car를 상속 받는다. 상속 덕분에 ElectricCar에서도 move()메서드를 사용할 수 있다.
package extends1.ex2;
public class GasCar extends Car {
 public void fillUp() {
 System.out.println("기름을 주유합니다.");
 }
}
  • 가솔린차도 마찬가지로 extends Car를 사용해서 부모 클래스인 Car를 상속 받는다.

단일 상속

자바는 다중 상속을 지원하지 않는다. extend 대상은 하나만 선택 가능

  • 자식이 부모를 2명 가질 수 없다.라고 이해하면 편함.

상속과 메모리 구조 (제일 중요)

  • 복습해서라도 이해 해야함
  1. 상속 관계의 객체를 생성하면 그 내부에는 부모와 자식이 모두 생성된다.
  2. 상속 관계의 객체를 호출할 때, 대상 타입을 정해야 한다. 이때 호출자의 타입을 통해 대상 타입을 찾는다.
  3. 현재 타입에서 기능을 찾지 못하면 상위 부모 타입으로 기능을 찾아서 실행한다ㅏ. 기능을 못 찾을 시 컴파일 오류 발생
  • 상속관계에서 인스턴스 생성 시 어떻게 되는지
    ElectricCar electricCar = new ElectricCar();
  • electricCar.charge() 호출
  • electricCar.move() 호출

정리

  1. 인스턴스를 생성할 때 인스턴스는 실제로는 자식(ElectricCar)으로 생성이 되지만, 내부에는 부모와 자식이 다 생긴다.
  2. 기능을 호출하면 자식과 부모 중에 시작하는 부분을 찾아야 하는데 호출하는 클래스를 기준으로 선택한다.
  3. 현재 타입에서 기능을 찾지 못하면 상위 부모 타입으로 기능을 찾아서 실행. 기능을 찾지 못하면 컴파일 오류 발생.

개념정리를 잘해둬야 함. 뒤에서 다형성을 이해하는데 큰 도움이 됨.


상속과 기능 추가

기존 코드를 유지하기 위해 ex3 패키지를 새로 만들자

package extends1.ex3;
public class Car {
 public void move() {
 System.out.println("차를 이동합니다.");
 }
 //추가
 public void openDoor() {
 System.out.println("문을 엽니다.");
 }
}
  • 부모 클래스에 문열기 기능을 추가한다. 자식들은 모두 물려받는다.
package extends1.ex3;
//추가
public class HydrogenCar extends Car {
 public void fillHydrogen() {
 System.out.println("수소를 충전합니다.");
 }
}
  • 수소차를 추가하고, 상속받은 덕분에 move() 메서드와 openDoor() 메서드와 같은 기능을 바로 사용 가능하다.
package extends1.ex3;

public class CarMain {
    public static void main(String[] args) {
        ElectricCar electricCar = new ElectricCar();
        electricCar.move();
        electricCar.charge();
        electricCar.openDoor();

        GasCar gasCar = new GasCar();
        gasCar.move();
        gasCar.fillUp();
        gasCar.openDoor();

        HydrogenCar hydrogenCar = new HydrogenCar();
        hydrogenCar.move();
        hydrogenCar.fillHydrogen();
        hydrogenCar.openDoor();
    }
}

  • 기능 추가만 했다.

상속과 메서드 오버라이딩

부모타입의 기능을 자식이 다르게 저장하고 싶은 경우

package extends1.overriding;

public class ElectricCar extends Car {

    @Override
    public void move() {
        System.out.println("전기차를 빠르게 이동합니다.");
    }
    public void charge() {
        System.out.println("충전합니다.");
    }
}
  • ElectricCar는 Car의 move()메서드 기능을 그대로 사용하고 싶지 않다. 그래서 ElectricCar의 move() 메서드를 새로 만들었다. 이렇게 부모의 기능을 자식이 새로 재정의하는 것을 메서드 오버라이딩이라고 한다.

@Override

  • @ 이 붙은 부분을 애노테이션이라 한다. 애노테이션은 주석과 비슷한데, 프로그램이 읽을 수 있는 특별한 주석이라 생각하면 된다. 애노테이션에 대한 자세한 내용은 따로 설명한다.
  • 이 애노테이션은 상위 클래스의 메서드를 오버라이드하는 것임을 나타낸다.

안써도 문제는 없다. 하지만 코드의 명확성을 보여주므로 그냥 쓰자.

오버로딩과 오버라이딩

  • 메서드 오버로딩 : 메서드 이름이 같고 매개변수(파라미터)가 다른 메
    서드를 여러 개 정의 하는 것, 같은 이름의 메서드를 여러개 정의했다고 이해하면 편함.

  • 메서드 오버라이딩 : 하위 클래스에서 상위 클래스의 메서드를 재정의하는 과정, 자식의 새로운 기능이 부모의 기존 기능을 넘어 타서 새로운 기능으로 덮어버린다고 이해하면 편함.

메서드 오버라이딩 조건

  • 메서드 이름: 메서드 이름이 같아야 한다.

  • 메서드 매개변수(파라미터): 매개변수(파라미터) 타입, 순서, 개수가 같아야 한다.

  • 반환 타입: 반환 타입이 같아야 한다. 단 반환 타입이 하위 클래스 타입일 수 있다.

  • 접근 제어자: 오버라이딩 메서드의 접근 제어자는 상위 클래스의 메서드보다 더 제한적이어서는 안된다. 예를 들어, 상위 클래스의 메서드가 protected 로 선언되어 있으면 하위 클래스에서 이를 public 또는 protected 로 오버라이드할 수 있지만, private 또는 default 로 오버라이드 할 수 없다.

  • 예외: 오버라이딩 메서드는 상위 클래스의 메서드보다 더 많은 체크 예외를 throws 로 선언할 수 없다. 하지만 더 적거나 같은 수의 예외, 또는 하위 타입의 예외는 선언할 수 있다. 예외를 학습해야 이해할 수 있다. 예외는 뒤에서 다룬다.

  • static , final , private : 키워드가 붙은 메서드는 오버라이딩 될 수 없다.

    • static 은 클래스 레벨에서 작동하므로 인스턴스 레벨에서 사용하는 오버라이딩이 의미가 없다. 쉽게 이
      야기해서 그냥 클래스 이름을 통해 필요한 곳에 직접 접근하면 된다.
    • final 메서드는 재정의를 금지한다.
    • private 메서드는 해당 클래스에서만 접근 가능하기 때문에 하위 클래스에서 보이지 않는다. 따라서 오버라이딩 할 수 없다.
  • 생성자 오버라이딩: 생성자는 오버라이딩 할 수 없다.


상속과 접근 제어

  • +: public
  • #: protected
  • ~: default
  • -: private

접근 제어자의 종류

  • private : 모든 외부 호출을 막는다.
  • default (package-private): 같은 패키지안에서 호출은 허용한다.
  • protected : 같은 패키지안에서 호출은 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.
  • public : 모든 외부 호출을 허용한다.

순서대로 private 이 가장 많이 차단하고, public 이 가장 많이 허용한다.
private -> default -> protected -> public

super

= 부모 클래스의 참조

package extends1.super1;
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); //this 생략 가능
        System.out.println("super value = " + super.value);

        this.hello();
        super.hello();
    }
}

package extends1.super1;

public class Parent {

    public String value = "parent";

    public void hello() {
        System.out.println("Parent.hello");
    }
}

package extends1.super1;

public class Super1Main {

    public static void main(String[] args) {
        Child child = new Child();
        child.call();
    }
}

  • this는 자기 자신 참조, parent는 부모 참조이다.

package extends1.super2;

public class ClassA {

    public ClassA() {
        System.out.println("ClassA 생성자");
    }
}
  • 최상위 부모 클래스

package extends1.super2;

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()를 사용해서 부모 클래스의 생성자를 호출해야 한다.
    • 예외로 생성자 첫줄에 this를 사용할 수 있다. 하지만 super는 자식의 생성자 안에서 언젠가는 반드시 호출해야 한다.
  • 부모 클래스의 생성자가 기본 생성자인 경우에는 super()를 생략할 수 있다.

package extends1.super2;

public class ClassC extends ClassB{

    public ClassC() {
        super(10,20);
        System.out.println("ClassC 생성자");
    }
    }
  • ClassC는 ClassB를 상속 받았다. ClassB (int a), ClassB(int a, int b)
  • 생성자는 하나만 호출할 수 있다. 두 생성자 중에 하나를 선택하면 된다. super(10, 20)를 통해 부모 클래스의 Class(int a, int b) 생성자를 선택했다.

package extends1.super2;

public class Super2Main {

    public static void main(String[] args) {
        ClassC classC = new ClassC();

    }
}

  • 실행 순서 ClassA -> ClassB -> ClassC 순서로 실행된다. 생성자의 실행 순서가 결과적으로 최상위 부모부터 실행되어서 하나씩 아래로 내려오는 것이다.
  • 상속 관계의 생성자 호출은 결과적으로 부모에서 자식 순서로 실행된다. 따라서 부모의 데이터를 먼저 초기화하고 그 다음에 자식의 데이터를 초기화한다.
  • 상속 관계에서 자식 클래스의 생성자 첫줄에 반드시 super를 호출해야 한다. 단 기본 생성자(super)인 경우 생략 할 수 있다.

클래스와 메서드에 사용되는 final

클래스에 final

  • 상속 끝
  • final로 선언된 클래스는 확장될 수 없다. 다른 클래스가 final로 선언된 클래스를 상속받을 수 있다.
  • 예: public final class MyfinalClass {...} -> 상속을 더 이상 할 수 없음.

메서드에 final

  • 오버라이딩 끝!
  • final로 선언된 메서드는 오버라이드 할 수 없다. 상속 받은 서브 클래스에서 이 메서드를 변경할 수 없다.
  • 예 public final void myFinalMethod() {...}

0개의 댓글