4. 상속


javalogo

Overview

상속(inheritance)은 이전에 정의된 클래스들을 재사용하는데 아주 유용한 객체 지향 개념(object-oriented concept)의 하나이다.

이 상속 모델의 기반은 is-a 관계이다. “Student is-a Person”, “Undergraduate is-a Student”는 이 is-a 관계의 예가 된다.

즉, “학생은 사람의 일종이다” “학부생은 학생의 일종이다”와 같은 것은 모두 is-a 관계의 예로 계층 구조를 이루게 되는데, 이 때 Student와 Undergraduate는 하나의 클래스 계층(class hierarchy)을 형성한다.

상속은 바로 하위 클래스가 상위 클래스의 모든 필드와 메소드를 마치 자기 것인양 사용할 수 있다는 것을 말한다.

따라서 하위 클래스는 상위 클래스를 확장시킨 것과 같다.


클래스 계층

기본 클래스(base class)란 클래스 확장의 기본이 되는 클래스로, 이로부터 확장된 파생 클래스(derived class)는 기본 클래스의 모든 속성, 필드와 메소드를 상속 받는다.

뿐만 아니라 파생 클래스에서는 필요한 데이터 필드나 메소드를 추가할 수 있고 상속받은 메소드를 재정의 할 수도 있다.

그러나 기본 클래스는 파생 클래스의 어떤 변경에도 전혀 영향을 받지 않는다.

이러한 특성은 소프트웨어의 유지 보수작업을 아주 효율적으로 수행할 수 있도록 한다.

상속이 기초로 하고 있는 is-a 관계는 이행적(transitive)이다. 다시 말해 X is-a Y이고, Y is-a Z이면 X is-a Z가 된다.

클래스 계층 구조에서 파생 클래스는 기본 클래스의 서브 클래스(sub class)라고 하고 기본 클래스는 파생 클래스의 슈퍼 클래스(super class)라고 한다.


접근 제어

클래스 확장 관계를 나타내는 슈퍼-서브 클래스 관계는 키워드 extends를 사용한다.

extends는 한 클래스가 다른 클래스로부터 파생되었다는 것을 표현하는 동시에 상속관계를 나타낸다.

클래스의 멤버에 대한 접근은 필요에 따라 적절히 public, private, protect를 사용하여 제어할 수 있다.

슈퍼 클래스에서 public으로 명세된 모든 멤버는 서브 클래스에 그대로 상속되어 사용된다.

반면에 private로 명세된 멤버들은 그 클래스의 메소드를 통해서만 접근할 수 있다.

따라서 슈퍼 클래스의 private 멤버들은 서브 클래스에서 접근할 수 있도록 하기 위해서는 접근자 메소드(getter)와 수정자 메소드(setter)를 제공하여야 한다.

protected로 명세된 멤버는 서브 클래스에만 상속되어 사용될 수 있고 다른 클래스들에게는 private가 된다.


ababab추상 메소드와 추상 클래스

클래스 정의에서 메소드를 선언만 하고 구현은 서브클래스에 위임시켜 놓기를 원할때가 있다.

이와 같이 구현은 없이 선언만 해 좋은 메소드를 추상 메소드(abstract method)라고 하고, 이런 추상 메소드를 포함하고 있는 클래스를 추상 클래스(abstract class)라고 한다.

추상 메소드와 추상 클래스는 모두 키워드 abstract로 명세한다.

클래스가 추상 메소드를 하나라도 포함하게 되면 그 클래스는 추상 클래스가 된다.

abstract class Person {                 // 추상 클래스
    private String name;
    abstract public void writeOutput(); // 추상 메소드

    public Person() {                   // 무인자 생성자
        name = "no name";
    }

    public Person(String s) {           // 1-인자 생성자
        name = s;
    }

    public void setName(String x) {     // 수정자
        name = x;
    }

    public String getName() {           // 접근자
        return name;
    }
}

위 프로그램은 Person 추상 클래스를 보여주고 있다.

Person 객체의 데이터를 저장하는 String 타입 name이 private로 선언되어 서브 클래스에서는 직접 접근할 수 없다.

writeOutput() 추상 메소드가 선언되어 있는데 Person 클래스의 서브 클래스가 구현해야 될 메소드이다.

Person 클래스는 추상 클래스이므로 생성자를 직접 호출되게 할 수 없지만 서브 클래스가 슈퍼 클래스의 private 필드들을 초기화 하기 위해 호출할 수 있는 생성자가 필요하다.

Person 클래스의 생성자는 내부의 name 데이터 필드를 초기화 한다.

setName() 메소드는 모든 서브 클래스에서 사용할 수 있도록 구현되어 있기 때문에 추상 메소드가 아니다.

이것은 private 데이터 필드 name의 값을 수정할 수 있는 수정자이다.

getName() 메소드는 private 데이터 필드 name의 값을 검색하는 접근자이다.


클래스의 확장

다음 프로그램은 Person의 서브 클래스로 Student 클래스를 정의한 예를 보여준다.

public class Student extends Person {           // Student는 Person의 서브 클래스
    private int id;

    public Student() {                          // 무인자 생성자
        super();
        id = 0;
    }

    public Student(String s, int sno) {         // 2-인자 생성자
        super(s);
        id = sno;
    }

    public void writeOutput() {                 // 추상 메소드 구현
        System.out.println("name : " + getName());
        System.out.println("Student number : " + id);
    }

    public void reset(String x, int newId) {
        setName(x);
        id = newId;
    }

    public int getId() {
        return id;
    }

    public void setId(int sno) {
        id = sno;
    }
}

새로 확장된 서브 클래스는 초기화를 위해 생성자를 정의해야 한다.

예를 들어 클래스 Student는 상속받은 데이터 필드와 자신의 데이터 필드 id를 초기화하기 위한 생성자가 필요하다.

먼저 슈퍼 클래스 Person의 생성자를 super()로 호출하여 Person으로부터 상속받은 데이터 필드에 대한 초기화를 수행하고 추상 메소드 writeOutput()를 구현한다.

만약 이 추상 메소드를 구현하지 않으면 Student 클래스도 추상 클래스가 되어야 하기 때문에 컴파일시 에러가 발생한다.


인터페이스

같은 이름의 메소드라도 이것을 적용하는 클래스마다 환경이 달라 알맞게 구현하여 사용하도록 하는 것이 바람직 할 때가 있기 때문에, 여러 클래스가 사용하는 메소드를 단순히 선언만 해놓을 필요가 있다.

이와 같이 구현없이 선언만 해놓은 메소드들의 집합을 인터페이스(interface)라고 한다.

인터페이스는 interface라는 키워드를 사용하는 것을 제외하고는 class와 비슷하다.

따라서 인터페이스도 클래스와 같이 상위 인터페이스로부터 상속을 받을 수 있다.

인터페이스와 추상 클래스는 모두 공통적으로 서브 클래스가 구현해야 될 메소드를 선언만 하고 있지만, 주된 차이점은 인터페이스에는 구현된 메소드가 하나도 포함될 수 없다는 것이다.

다음 프로그램은 인터페이스의 한 예로 Comparable 인터페이스를 보여주고 있다.

public interface Comparable {
    int compareTo(Comparable x);    //클래스가 구현해야 될 추상 메소드
}

이 Comparable 인터페이스는 클래스가 구현해야 될 하나의 추상 메소드 compareTo()를 명세하고 있다.

한가지 유의할 것은 이너페이스에 명세된 메소드들은 구현도 없을 뿐 아니라 public이나 abstract로 명세하지 않는다는 것이다.

인터페이스를 구현하려는 클래스는 키워드 implements와 구현하려는 인터페이스 이름을 명세하고, 인터페이스에 선언되어 있는 모든 메소드를 구현해야 한다.

인터페이스 이름은 참조변수를 선언할 때 타입 이름으로 사용할 수 있다.

그러나 이 인터페이스 타입으로 선언된 참조변수는 인터페이스는 객체를 생성할 수 없기 때문에, 실제로 이 인터페이스를 구현하는 클래스의 객체를 참조하게 된다.

다음 프로그램은 Comaparable 인터페이스를 구현하는 Height 라는 클래스를 예로 보여준다.

public class Height implements Comparable {
    private int value;

    public Height(int x) {
        value x;
    }

    public String toString() {
        return Integer.toString(value);
    }

    public int compareTo(Comparable x) {    // 인터페이스의 구현
        return value < ((Height)x).value ? -1 :
            value == ((Height)x).value ? 0 : 1;
    }
}

참고 문서

  • 이석호. (2004). 자료 구조와 JAVA. 정익사.





© 2019. by RaP0d

Powered by aiden