상속

목표 및 학습할 것

  • 목표 : 자바의 상속에 대해 학습하세요.

자바 상속의 특징

자바 상속이란 기존에 있는 클래스를 재사용함으로써, 코드의 가용성 및 유지보수 측면에서 용이합니다.

class Child extends Parent {

}

간단히 extends 키워드를 써서 사용할 수 있습니다.

상속 관계를 맺으면 한쪽은 조상 클래스, 다른 한쪽은 자손 클래스가 됩니다. 자손 클래스는 조상 클래스가 가지고 있는 변수 혹은 함수를 사용할 수 있습니다.


super 키워드

super 키워드는 자손 클래스에서 조상 클래스의 맴버를 참조할 때 사용합니다.


메소드 오버라이딩

오버라이딩은 조상 클래스에서 상속받은 메소드를 자손 클래스에서 변경해서 사용하는 것입니다. 예를 들면

class Point {
    int x;
    int y;

    String getLocation() {
        return "x: " + x + ", y :" + y;
    }
}

class Point3D extends Point {
    int z;

    String getLocation() {
        return "x :" + x + ", y :" + y + ", z :" + z;
    }
}

Point3D는 3차원 좌표계를 표현하기 위해 자신에 맞게 z축 좌표값도 포함하여 반환하도록 오버라이딩을 하였습니다.

오버라이딩을 쓰기 위해서는 아래와 같은 조건을 충족시켜야 합니다.

자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와

  1. 이름이 같아야 한다.
  2. 매개변수가 같아야 한다.
  3. 반환타입이 같아야 한다.

다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

다이나믹 메소드 디스패치는 컴파일 타임이 아닌 런 타임에 오버로딩된 함수를 호출하는 기법으로 자바의 다형성을 보여줍니다.

class A {
    void m1() {
        System.out.println("Inside A's m1 method");
    }
}

class B extends A {
    // overriding m1()
    void m1() {
        System.out.println("Inside B's m1 method");
    }
}

class C extends A {
    // overriding m1()
    void m1() {
        System.out.println("Inside C's m1 method");
    }
}

// Driver class
class Dispatch {
    public static void main(String args[]) {
        A a = new A();
        B b = new B();
        C c = new C();

        // object a reference of type A
        A ref;

        // ref refers to an A object, calling A's version of m1()
        ref = a;
        ref.m1();

        // now ref referes to a B object, calling B's version of m1()
        ref = b;
        ref.m1();

        // now ref referes to a C object, calling C's version of m1()
        ref = c;
        ref.m1();
    }
}

m1의 어떤 버전(A,B,C 중)을 부르느냐는 호출시점의 어떤 오브젝트를 참조하느냐에 따라 결정됩니다.


추상 클래스

  • 추상 클래스는 미완성된 메서드를 포함하고 있는 클래스입니다.
  • 그러므로 추상 클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있습니다.

추상 클래스의 장점으로는 여러 클래스들 간의 공통부분이 존재할 경우 이 부분을 추상 클래스를 만들어 놓고, 이를 이용해 각각 클래스를 완성하는 것이 효율적입니다.

구현

추상 클래스는 키워드 abstract을 붙여 작성합니다.

추상 메서드

추상 메서드 또한 키워드 abstract을 붙여 작성합니다.


final 키워드

final 키워드는 제어자 중 하나입니다.

  • 제어자의 종류는 아래와 같습니다.
    • 접근 제어자 : public, protected, default, private
    • 그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

주의 할 점으로, 접근 제어자는 한 번에 네 가지 중 하나만선택해서 사용할 수 있습니다. 예로, public과 private을 함께 사용할 수 없습니다.

final 키워드의 사용

  • 변수에 사용되면 값을 변경할 수 없는 상수가 됩니다.
  • 메서드에 사용되면 오버라이딩을 할 수 없게 됩니다.
  • 클래스에 사용되면 자신을 확장하는 자손클래스를 정의하지 못하게 됩니다.
생성자를 이용한 final멤버 변수의 초기화
class Card {
    final int NUMBER;   // 상수지만 선언과 함께 초기화 하지 않고,
    final String KIND;  // 생성자에서 단 한번만 초기화 가능

    Card(String kind, int num) {
        KIND = kind;
        NUMBER = num;
    }
}

Object 클래스

Object 클래스는 모든 클래스 상속계층도의 최상위에 있는 조상클래스입니다.

아래와 같이 상속을 받지 않는 Monitor 클래스를 정의했다면

class Monitor {
    ...
}

위의 코드를 컴파일 하면 컴파일러는 자동적으로 ‘extends Object’을 추가하여 Object 클래스를 상속받도록 합니다.

class Monitor extends Object {
    ...
}

이렇게 함으로써 Object 클래스가 모든 클래스의 조상이 되도록 합니다.

이러한 특성의 장점으로는 Object 클래스의 맴버들을 상속 받기 때문에 Object 클래스에 정의된 멤버들을 사용할 수 있습니다. (toString(), equals() 등등)


상속과 코드 재사용

객체지향 프로그래밍의 장점 중 하나는 코드를 재사용하기 용이하다는 점입니다. 이런 관점에서, 클래스를 재사용하기 위해 새로운 클래스를 추가하는 가장 대표적인 기법이 상속입니다.

코드의 재사용은 결국 중복 코드를 어떻게 관리할 것인가?에 대한 해답을 찾는 과정입니다. 이에 대한 키워드로는 대표적으로 DRY 원칙이 있습니다.

DRY 원칙

Don’t Repeat Yourself의 약자로, 신뢰할 수 있고 수정하기 쉬운 소프트웨어를 만들기 위해 중복을 제거하는 방법입니다.

  • 중복 여부를 판단하는 기준은, 요구사항이 변경됐을 때 두 코드를 함께 수정해야 한다면 이 코드는 중복입니다.
  • DRY 원칙은 다른 이름으로 Once and Only Once 원칙, 또는 Single-Point Control 원칙이라고도 부른다.

상속을 사용함에 따른 대표적 문제로는 부모 클르래스와 자식 클래스 사이의 강한 결함을 가져온다는 것입니다. 이처럼 상속 관계로 연결된 자식 클래스가 부모 클래스의 변경에 취약해지는 현상을 가리켜 취약한 기반 클래스 문제라고 합니다.

취약한 기반 클래스 문제 (Fragile Base Class Problem, Brittle Base Class Problem)

이 문제는 상속을 사용한다면 피할 수 없는 객체지향 프로그래밍의 근본적인 취약성입니다.

  • 쉽게, 상속이라는 문맥 안에서 결합도가 초래하는 문제접을 이야기합니다.
  • 이 문제는 캡슐화를 약화시키고 결합도를 높힙니다.

단순히 글로만 읽으니 와닿지 않는 부분이 있어 예제를 추가합니다.

불필요한 인터페이스 상속 문제

자바의 초기 버전에서 상속을 잘못 사용한 사례는 java.util.Propertiesjava.util.Stack이 있습니다.

  • 부모 클래스에서 상속받은 메서드를 사용할 경우 자식 클래스의 규칙이 위반 될 수 있습니다.

Vector와 Stack 상속 관계

출처 : http://www.yes24.com/Product/Goods/74219491

  • Vector는 임의의 위치에서 요소를 추출하고 삽입할 수 있는 리스트 자료 구조의 구현체
  • Stack은 가장 나중에 추가된 요소가 가장 먼저 추출되는 (Last In First Out, LIFO) 자료 구조인 스택을 구현한 클래스

자바의 초기 컬렉션 프레임워크 개발자들은 요소의 추가, 삭제 오퍼레이션을 제공하는 Vector를 재사용하기 위해 Stack을 Vector의 자식 클래스로 구현했습니다.

여기서 문제는, Stack에게 상속된 Vector의 퍼블릭 인터페이스를 이용하면 임의의 위치에서 요소를 추가하거나 삭제할 수 있습니다. 따라서, 맨 마지막 위치에서만 요소를 추가하거나 제거할 수 있도록 허용하는 Stack의 규칙을 쉽게 위반할 수 있습니다.

해결책 - 취약한 기반 클래스 문제
  1. 자식 클래스가 부모클래스의 구현이 아닌 추상화에 의존하도록 만들어야 합니다.
  2. 중복 코드 안에서 차이점을 별도의 메서드로 추출합니다.
  3. 중복 코드를 부모 클래스로 올립니다.

출처 자바의 정석 https://www.geeksforgeeks.org/dynamic-method-dispatch-runtime-polymorphism-java/ 오브젝트 http://www.yes24.com/Product/Goods/74219491