Skip to content

10. 상속(Inheritance)


1. 키워드

  • 상속(Inheritance)
  • 부모 클래스(Parent Class), 상위 클래스(Super Class), 기초 클래스(Base Class)
  • 자식 클래스(Child Class), 하위 클래스(Sub Class), 파생 클래스(Derived Class)
  • Object 클래스
  • supersuper()
  • 메서드 오버라이딩(Method Overriding)


2. 상속(Inheritance)의 개념

  • 상속이란 기존의 클래스에 기능을 추가하거나 재정의하여 새로운 클래스를 정의하는 것을 의미한다.
  • 이러한 상속은 캡슐화, 추상화와 더불어 OOP를 구성하는 중요한 특징 중 하나이다.
  • 상속을 이용하면 기존에 정의되어 있는 모든 필드와 메서드를 물려받아, 새로운 클래스를 생성할 수 있다.
  • 이때 기존에 정의되어 있던 클래스를 부모 클래스(Parent Class) 또는 상위 클래스(Super Class), 기초 클래스(Base Class)라고도 한다.
  • 그리고 상속을 통해 새롭게 작성되는 클래스를 자식 클래스(Child Class) 또는 하위 클래스(Sub Class), 파생 클래스(Derived Class)라고도 한다.


1) 상속의 장점

  • 자바에서 클래스의 상속은 다음과 같은 장점을 가진다.


1] 기존에 작성된 클래스를 재활용할 수 있다.

2] 자식 클래스 설계 시 중복되는 멤버를 미리 부모 클래스에 작성해 놓으면, 자식 클래스에서는 해당 멤버를 작성하지 않아도 된다.

3] 클래스 간의 계층적 관계를 구성함으로써 다형성의 문법적 토대를 마련한다.


2) 자식 클래스(Child Class)

  • 자식 클래스란 부모 클래스의 모든 특성을 물려받아 새롭게 작성된 클래스를 의미한다.


  • 자바에서 자식 클래스는 다음과 같은 문법을 통해 선언한다.


class 자식클래스이름 extends 부모클래스이름 { ... }


  • 다음 그림은 부모 클래스와 자식 클래스 간의 포함 관계를 나타낸 그림이다.


001


  • 이처럼 부모 클래스는 자식 클래스에 포함된 것으로 볼 수 있다.
  • 따라서 부모 클래스에 새로운 필드를 하나 추가하면, 자식 클래스에도 자동으로 해당 필드가 추가된 것처럼 동작한다.
  • 자식 클래스에는 부모 클래스의 필드와 메서드만이 상속되며, 생성자와 초기화 블록은 상속되지 않는다.
  • 또한, 부모 클래스의 접근 제어가 private이나 default로 설정된 멤버는 자식 클래스에서 상속받지만 접근할 수는 없다.


class Parent {

  private int a = 10; // private 필드
  public int b = 20; // public 필드
}

class Child extends Parent {

  public int c = 30; // public 필드

  void display() {
    System.out.println(a); // 상속받은 private 필드 참조 -> 컴파일 오류 발생
    System.out.println(b); // 상속받은 public 필드 참조
    System.out.println(c); // 자식 클래스에서 선언한 public 필드 참조
  }
}

class Test {

  public static void main(String[] args) {
    Child ch = new Child();

    ch.display();
  }
}

// 20
// 30


  • 위 예제의 ②번 라인에서는 자식 클래스의 메서드에서 부모 클래스에서 상속받은 public 필드를 참조하고 있다.
  • 이처럼 자식 클래스에서 따로 선언하지 않은 필드라도 해당 이름의 필드를 부모 클래스에서 상속받았다면 문제가 없다.
  • 하지만 ①번 라인처럼 해당 필드가 부모 클래스의 private 필드라면 접근할 수 없으므로, 주석 처리를 하지 않으면 컴파일 시 오류를 발생시킬 것이다.
  • 또한 자식 클래스에서는 ③번 라인처럼 자신만의 필드나 메서드를 선언하여 사용할 수 있다.


Note

  • 자바에서 클래스는 단 한 개의 클래스만을 상속받는 단일 상속만 가능하다.


3) Object 클래스

  • 자바에서 Object 클래스는 모든 클래스의 부모 클래스가 되는 클래스이다.
  • 따라서 자바의 모든 클래스는 자동으로 Object 클래스의 모든 필드와 메서드를 상속받게 된다.
  • 즉, 자바의 모든 클래스는 별도로 extends 키워드를 사용하여 Object 클래스의 상속을 명시하지 않아도 Object 클래스의 모든 멤버를 자유롭게 사용할 수 있다.
  • 자바의 모든 객체에서 toString()이나 clone()과 같은 메서드를 바로 사용할 수 있는 이유가 해당 메서드들이 Object 클래스의 메서드이기 때문이다.


3. supersuper()

1) super 키워드

  • super 키워드는 부모 클래스로부터 상속받은 필드나 메서드를 자식 클래스에서 참조하는 데 사용하는 참조 변수이다.
  • 인스턴스 변수의 이름과 지역 변수의 이름이 같을 경우 인스턴스 변수 앞에 this 키워드를 사용하여 구분할 수 있었다.
  • 이와 마찬가지로 부모 클래스의 멤버와 자식 클래스의 멤버 이름이 같을 경우 super 키워드를 사용하여 구별할 수 있다.
  • 이렇게 자바에서는 super 참조 변수를 사용하여 부모 클래스의 멤버에 접근할 수 있다.
  • this와 마찬가지로 super 참조 변수를 사용할 수 있는 대상도 인스턴스 메서드뿐이며, 클래스 메서드에서는 사용할 수 없다.


class Parent {

  int a = 10;
}

class Child extends Parent {

  void display() {
    System.out.println(a); // 10
    System.out.println(this.a); // 10
    System.out.println(super.a); // 10
  }
}

class Test {

  public static void main(String[] args) {
    Child ch = new Child();

    ch.display();
  }
}


  • 위의 예제에서 int형 변수 a는 부모 클래스인 Parent 클래스에서만 선언되어 있다.
  • 따라서 지역 변수와 this 참조 변수 그리고 super 참조 변수 모두 같은 값을 출력한다.


class Parent {

  int a = 10;
}

class Child extends Parent {

  int a = 20;

  void display() {
    System.out.println(a); // 20
    System.out.println(this.a); // 20
    System.out.println(super.a); // 10
  }
}

class Test {

  public static void main(String[] args) {
    Child ch = new Child();

    ch.display();
  }
}


  • 하지만 위의 예제에서 int형 변수 a는 자식 클래스인 Child 클래스에서도 선언되어 있다.
  • 따라서 지역 변수와 this 참조 변수는 자식 클래스에서 대입된 값을 출력하며, super 참조 변수만이 부모 클래스에서 대입된 값을 출력하게 된다.


2) super() 메서드

  • this() 메서드가 같은 클래스의 다른 생성자를 호출할 때 사용된다면, super() 메서드는 부모 클래스의 생성자를 호출할 때 사용된다.
  • 자식 클래스의 인스턴스를 생성하면, 해당 인스턴스에는 자식 클래스의 고유 멤버뿐만 아니라 부모 클래스의 모든 멤버까지도 포함되어 있다.
  • 따라서 부모 클래스의 멤버를 초기화하기 위해서는 자식 클래스의 생성자에서 부모 클래스의 생성자까지 호출해야만 한다.
  • 이러한 부모 클래스의 생성자 호출은 모든 클래스의 부모 클래스인 Object 클래스의 생성자까지 계속 거슬러 올라가며 수행된다.


  • 따라서 자바 컴파일러는 부모 클래스의 생성자를 명시적으로 호출하지 않는 모든 자식 클래스의 생성자 첫 줄에 자동으로 다음과 같은 명령문을 추가하여, 부모 클래스의 멤버를 초기화할 수 있도록 해준다.


super();


  • 하지만 자바 컴파일러는 컴파일 시 클래스에 생성자가 하나도 정의되어 있지 않아야만, 자동으로 기본 생성자를 추가해준다.


  • 만약 다음 예제처럼 부모 클래스에 매개변수를 가지는 생성자를 하나라도 선언했다면, 부모 클래스에는 기본 생성자가 자동으로 추가되지 않을 것이다.


class Parent {

 int a;

 Parent(int n) {
  a = n;
 }
}


  • 따라서 다음 예제처럼 Parent 클래스를 상속받은 자식 클래스에서 super() 메서드를 사용하여 부모 클래스의 기본 생성자를 호출하게 되면, 오류가 발생하게 될 것이다.


class Parent {

 int a;

 Parent(int n) {
  a = n;
 }
}

class Child extends Parent {

 int b;

 Child() {
  super(); // 오류 발생
  b = 20;
 }
}


  • 왜냐하면 부모 클래스인 Parent 클래스에는 기본 생성자가 추가되어 있지 않기 때문이다.


  • 따라서 매개변수를 가지는 생성자를 선언해야 할 경우에는 되도록이면 다음 예제처럼 기본 생성자까지 명시적으로 선언하는 것이 좋다.


class Parent {

 int a;

 Parent() { // 매개변수를 가지지 않는 기본 생성자
  a = 10;
 }

 Parent(int n) { // 매개변수를 가지는 생성자 -> 기본 생성자를 명시적으로 선언해야 함
  a = n;
 }
}

class Child extends Parent {

 int b;

 Child() {
  super(); // 매개변수를 가지지 않는 기본 생성자 호출
  b = 20;
 }
}


  • 다음 예제는 super() 메서드가 어떻게 호출되는지를 보여주는 예제이다.


class Parent {

  int a;

  Parent() {
    a = 10;
  }

  Parent(int n) {
    a = n;
  }
}

class Child extends Parent {

  int b;

  Child() {
    // super(40);
    b = 20;
  }

  void display() {
    System.out.println(a);
    System.out.println(b);
  }
}

class Test {

  public static void main(String[] args) {
    Child ch = new Child();

    ch.display();
  }
}

// 10
// 20


  • 위의 예제를 그냥 실행하면, 자바 컴파일러는 주석 처리된 ①번 라인에 자동으로 super(); 구문을 삽입할 것이다.
  • 따라서 변수 a10으로 초기화된다.


class Parent {

  int a;

  Parent() {
    a = 10;
  }

  Parent(int n) {
    a = n;
  }
}

class Child extends Parent {

  int b;

  Child() {
    super(40);
    b = 20;
  }

  void display() {
    System.out.println(a);
    System.out.println(b);
  }
}

class Test {

  public static void main(String[] args) {
    Child ch = new Child();

    ch.display();
  }
}

// 40
// 20


  • 하지만 ①번 라인의 주석 처리를 해제하고 실행하면, 부모 클래스인 Parent 클래스는 두 번째 생성자에 의해 초기화될 것이다.
  • 따라서 변수 a40으로 초기화된다.


4. 메서드 오버라이딩(Method Overriding)

  • 앞서 배운 오버로딩(Overloading)이란 서로 다른 시그니처를 갖는 여러 메서드를 하나의 이름으로 정의하는 것이었다.
  • 오버라이딩이란 상속 관계에 있는 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 같은 시그니처를 갖는 메서드로 다시 정의하는 것이라고 할 수 있다.
  • 자바에서 자식 클래스는 부모 클래스의 private 멤버를 제외한 모든 메서드를 상속받는다.
  • 이렇게 상속받은 메서드는 그대로 사용해도 되고, 필요한 동작을 위해 재정의하여 사용할 수도 있다.
  • 즉, 메서드 오버라이딩이란 상속받은 부모 클래스의 메서드를 재정의하여 사용하는 것을 의미한다.


1) 메서드 오버라이딩의 조건

  • 자바에서 메서드를 오버라이딩하기 위한 조건은 다음과 같다.


1] 오버라이딩이란 메서드의 동작만을 재정의하는 것이므로, 메서드의 선언부는 기존 메서드와 완전히 같아야 한다. 하지만 메서드의 반환 타입은 부모 클래스의 반환 타입으로 타입 변환할 수 있는 타입이라면 변경할 수 있다.

2] 부모 클래스의 메서드보다 접근 제어자를 더 좁은 범위로 변경할 수 없다.

3] 부모 클래스의 메서드보다 더 큰 범위의 예외를 선언할 수 없다.


  • 자바에서는 메서드 오버라이딩을 통해 상속받은 부모 클래스의 메서드를 자식 클래스에서 직접 재정의할 수 있다.


  • 다음 예제는 부모 클래스인 Parent 클래스의 display() 메서드를 자식 클래스인 Child 클래스에서 오버라이딩하는 예제이다.


class Parent {

  void display() {
    System.out.println("부모 클래스의 display() 메서드입니다.");
  }
}

class Child extends Parent {

  void display() {
    System.out.println("자식 클래스의 display() 메서드입니다.");
  }
}

class Test {

  public static void main(String[] args) {
    Parent pa = new Parent();
    pa.display(); // 부모 클래스의 display() 메서드입니다.

    Child ch = new Child();
    ch.display(); // 자식 클래스의 display() 메서드입니다.

    Parent pc = new Child();
    pc.display(); // 자식 클래스의 display() 메서드입니다.

  Child cp = new Parent(); // 컴파일 오류 발생
    cp.display(); // 컴파일 오류 발생
  }
}


  • 위의 예제에서 세 번째와 같은 인스턴스의 참조가 허용되는 이유는 바로 자바에서의 다형성(Polymorphism) 때문이다.


2) 오버로딩과 오버라이딩

  • 간단히 정의하면 오버로딩은 새로운 메서드를 정의하는 것이다.
  • 하지만 오버라이딩은 상속받은 기존의 메서드를 재정의하는 것이다.


  • 다음 예제는 부모 클래스인 Parent 클래스의 display() 메서드를 자식 클래스인 Child 클래스에서 오버라이딩과 오버로딩을 둘 다 수행하는 예제이다.


class Parent {

  void display() {
    System.out.println("부모 클래스의 display() 메서드입니다.");
  }
}

class Child extends Parent {

  void display() { // 오버라이딩된 display() 메서드
    System.out.println("자식 클래스의 display() 메서드입니다.");
  }

  void display(String str) { // 오버로딩된 display() 메서드
    System.out.println(str);
  }
}

class Test {

  public static void main(String[] args) {
    Child ch = new Child();

    ch.display();
    ch.display("오버로딩된 display() 메서드입니다.");
  }
}

// 자식 클래스의 display() 메서드입니다.
// 오버로딩된 display() 메서드입니다.


3) @Override 어노테이션

  • 메서드 오버라이딩을 통해 상속받은 부모 클래스의 메서드를 자식 클래스에서 직접 재정의할 때 개발자도 사람인지라 철자에 대한 실수를 하기 마련이다.
  • 이러한 실수를 방지할 목적으로 자바에서는 @Override 어노테이션을 지원한다.


class Parent {

  void display() {
    System.out.println("부모 클래스의 display() 메서드입니다.");
  }
}

class Child extends Parent {

  @Override // 어노테이션 사용
  void display() { // 오버라이딩된 display() 메서드
    System.out.println("자식 클래스의 display() 메서드입니다.");
  }

  // @Override -> 오버로딩에는 어노테이션 사용 안 함
  void display(String str) {
    System.out.println(str);
  }
}

class Test {

  public static void main(String[] args) {
    Child ch = new Child();

    ch.display();
    ch.display("오버로딩된 display() 메서드입니다.");
  }
}

// 자식 클래스의 display() 메서드입니다.
// 오버로딩된 display() 메서드입니다.

References