[STUDY HALLE] 6주차 과제: 상속

5 minute read

/* 실제로는 2021-03-16에 작성된 게시물입니다. */

학습 목표

자바의 상속에 대해 학습하세요.

  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스

Deadline: ~2020/12/26 13:00

상속

상속(inheritance)이란 기존의 클래스에 기능을 추가하거나 재정의하여 새로운 클래스를 정의하는 것을 의미한다. 자바에서는 새로 작성하는 클래스 이름 뒤에 상속받고자 하는 클래스 이름을 키워드 ‘extends’와 함께 작성해주면 된다.

class Child extends Parent{
    ...
}

이때, 기존의 클래스를 부모 클래스(parent class) 또는 상위 클래스(super class) 라고도 한다. 그리고 상속을 통해 새롭게 작성되는 클래스를 자식 클래스(child class) 또는 하위 클래스(sub class)라고도 한다.

자손 클래스는 조상 클래스의 모든 멤버를 상속받으므로 항상 조상클래스보다 같거나 많은 멤버를 가진다. 즉, 상속을 거듭할수록 상속받는 클래스의 멤버 개수는 점점 늘어나게 된다. 또한, 하나의 조상 클래스가 여러 자손 클래스에게 상속해줄 수 있지만, 하나의 자손 클래스가 여러 부모 클래스를 상속받는 다중 상속은 자바에서 불가능하다. 8주차 스터디에서 다뤘듯이, 여러 부모 클래스들 중 어디의 메소드/변수를 상속받은 것인지 모호한 상황을 피하기 위함이다.

super

super는 부모 클래스로부터 상속받은 필드나 메소드를 자식 클래스에서 참조하는데 사용하는 참조 변수이다. this처럼 사용되며 자기 자신이 아닌 부모 클래스의 멤버에 접근한다는 것만 다르다.

package week6;

class Parent {
    int x = 10;
    int y = 11;
}

class Child extends Parent {
    int x = 20;

    Child() {
        System.out.println("x = " + x);
        System.out.println("this.x = " + this.x);
        System.out.println("super.x = " + super.x);
    }
}

public class Week6 {
    public static void main(String[] args){
        Child child = new Child();
    }
}
x = 20
this.x = 20
super.x = 10

super()
super() 메소드는 부모 클래스의 생성자 함수를 호출하는 메소드이다. 자식 클래스의 생성자는 첫 줄에서 super()를 호출해야한다. 그렇지 않으면 다음과 같은 경고문을 보게 되는데,

Call to 'super()' must be first statement in constructor body

자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 때문이다. 부모클래스에 기본 생성자가 있다면, 자식 클래스에서는 굳이 호출하지 않아도 컴파일러가 끼워넣어준다.

메소드 오버라이딩

  • 오버라이딩(overriding): 상속 관계에 있는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 시그니쳐를 갖는 메소드로 다시 정의하는 것
  • 오버로딩(overloading): 서로 다른 시그니처를 갖는 여러 메소드를 하나의 이름으로 정의하는 것

메소드 시그니처 : 메소드 명과 파라미터의 순서, 타입, 개수

부모클래스의 메소드를 오버라이딩하기 위해서는 다음과 같은 제약이 따른다.

  1. 오버라이딩이란 메소드의 동작만을 재정의하는 것이므로, 메소드의 선언부는 기존 메소드와 완전히 같아야 한다. 하지만 메소드의 반환 타입은 부모 클래스의 반환 타입으로 타입 변환할 수 있는 타입이라면 변경할 수 있다.
  2. 부모 클래스의 메소드보다 접근 제어자를 더 좁은 범위로 변경할 수 없다.
  3. 부모 클래스의 메소드보다 더 큰 범위의 예외를 선언할 수 없다.

오버라이딩한 메소드에는 @Override 어노테이션을 붙여줄 수 있는데, 꼭 붙여주어야 하는 것은 아니지만 부모클래스에 없는 메소드를 오버라이딩하려고 착각하는 것을 방지해줄 수 있다.

class Parent {
    int x = 10;
    int y = 11;

    void run() {
        System.out.println("I'm your father");
    }
}

class Child extends Parent {
    int x = 20;

    Child() {
        // System.out.println("x = " + x);
        // System.out.println("this.x = " + this.x);
        // System.out.println("super.x = " + super.x);
    }

    @Override
    void run() {
        System.out.println("I'm your son");
    }
}

public class Week6 {
    public static void main(String[] args){
        Parent parent = new Parent();
        Child child = new Child();
        Parent poly = new Child();

        parent.run();
        child.run();
        poly.run();
    }
}
I'm your father
I'm your son
I'm your son

위의 코드를 보면 조금 이상한 부분이 있다.

Parent poly = new Child();

이는 자바의 주요 개념중 하나인 다형성(polymorphism)으로 이해할 수 있다. 다형성이란 말 그대로 여러가지 형태를 가질 수 있는 것을 말하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록(== 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록) 함으로써 다형성을 프로그램적으로 구현하였다.
이때, 참조변수 ‘poly’의 참조 타입은 ‘Parent’이므로, 비록 인스턴스 타입은 ‘Child’이지만 ‘Parent’클래스의 멤버들만 사용할 수 있다. 하지만 인스턴스가 선언되어 자식 클래스에서 확장된 멤버는 메모리에 있기 때문에, 형변환을 이용하면 자식 클래스의 멤버를 사용할 수 있게 된다.

다이나믹 메소드 디스패치

메소드 디스패치(method dispatch)란 어떤 메소드를 호출할지 결정하여 실행시키는 과정으로, static(정적)과 dynamic(동적)이 있다. 위의 예시에서

Parent parent = new Parent();
Child child = new Child();

parent.run();
child.run();

두 경우는 컴파일 시점에 컴파일이 어떤 메소드를 호출할지 명확하게 알고 있는 정적 디스패치이다. 반면

Parent poly = new Child();
poly.run();

다형성이 적용된 위의 예시는 어떤 메소드를 호출할지 런타임에 결정되는 동적 디스패치이다. 런타임에 컴파일러가 알고있는 타입 정보를 가지고 어떤 메소드를 호출할지 결정하는 것이다.

추상 클래스

추상 메소드(abstract method)란 자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있는 메소드를 의미하는데, 하나 이상의 추상 메소드를 포함하는 클래스를 가리켜 추상 클래스(abstract class)라고 한다. 사실 추상 메소드가 없는 추상 클래스도 선언이 가능하기 때문에, ‘하나 이상의 추상 메소드를 가지는 클래스는 추상 클래스가 되어야한다’는 표현이 더 정확하다. 추상클래스로 인스턴스는 생성할 수 없기 때문에 추상클래스 자체로는 클래스로서의 역할을 못하지만, 새로운 클래스를 작성하는데 바탕이 되는 조상클래스로서 중요한 의미를 가진다.

abstract class 클래스이름 {
    abstract 반환타입 메소드이름();
    ...
}

추상클래스를 굳이 사용해야하는 때는 자식 클래스가 반드시 특정 메소드를 구현하도록 강제하고 싶을 때이다. 일반 메소드로 구현한다면 사용자에 따라 해당 메소드를 구현할 수도 있고, 안 할 수도 있지만, 추상 메소드가 포함된 추상 클래스를 상속받은 모든 자식 클래스는 추상 메소드를 구현해야만 인스턴스를 생성할 수 있으므로, 반드시 구현하게 된다.

추상클래스 vs 인터페이스

추상 메소드를 통해 ‘틀’을 제공한다는 점에서 인터페이스와 비슷하다고 느껴진다. 그렇다면 구체적인 차이점은 무엇일까?
가장 주된 차이점은 목적이 다르다는 것이다. 추상 클래스는 그 추상 클래스를 상속받아서 기능을 이용하고 확장시키고자 할 때 사용한다. 반면 인터페이스는 구현을 강제함으로써 구현 객체의 같은 동작을 보장하고자 하는 것에 있다. ‘다중 상속’이라는 주요한 차이점이 있지만, 그 차이점이 인터페이스의 존재 이유라고는 할 수 없다.

abstract class Animal {
    public String name;
    public String leg;

    /* Abstract method */
    Animal(String name){
        this.name = name;
    }
    abstract void run();
}

interface Rideable {
    void rideable(String riderName);
}

class Horse extends Animal implements Rideable {
    Horse(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name+"은(는) 네 발로 뛴다.");
    }

    @Override
    public void rideable(String riderName) {
        System.out.println(this.name+"은(는) "+riderName+"을(를) 태울 수 있다.");
    }
}

class Kangaroo extends Animal {
    Kangaroo(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name+"은(는) 네 발로 뛴다.");
    }
}

public class Main {
    public static void main(String[] args){
        Horse horse = new Horse("적토마");
        Kangaroo kangaroo = new Kangaroo("콩콩이");

        horse.run();
        horse.rideable("관우");

        kangaroo.run();
    }
}

동물 추상 클래스는 동물이 가지는 공통 속성인 이름과, 다리에 대한 필드를 가지고 있으며, 동물들의 공통적인 기능인 run 추상 메서드를 가지고 있다. 인터페이스는 탈 수 있는 이라는 의미를 가지고 있고 rideable 이라는 추상 메서드를 가지고 있으며 구현체에서는 추상 메서드를 오버라이딩 하여 사용해야 한다. 흔히 이해를 돕기 위하여 추상 클래스는 is a kind of의 관계일 때, 인터페이스는 be able to의 관계일 때 사용한다고 설명한다.

final

final 키워드는 클래스, 변수, 메소드 앞에 붙어 immutable하게 만들어 준다. 즉, 변수에 붙으면 값을 변경할 수 없는 상수가 되고, 메소드에 사용되면 오버라이딩할 수 없게 되고, 클래스에 사용되면 자식 클래스를 생성할 수 없게 된다.

Object 클래스

Object클래스는 모든 클래스의 부모 클래스이다. 다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 Object클래스로부터 상속받는다. 따라서 Object 클래스에 정의된 메소드들은 모든 인스턴스가 사용 가능하다.

메소드 설명
protected Object clone( ) 객체 자신의 복사본을 반환한다.
public boolean equals(Object obj) 객체 자신과 객체 obj가 같은 객체인지 알려준다.(같으면 true)
protected void finalize() 객체가 소멸될 때 가비지 컬렉터에 의해 자동적으로 호출된다.
public Class getClass() 객체 자신의 클래스 정보를 담고 있는 Class인스턴스를 반환한다.
public int hashCode() 객체 자신의 해시코드를 반환한다.
public String toString() 객체 자신의 정보를 문자열로 반환한다.
public void notify() 객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다.
public void notifyAll() 객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다.
public void wait(long timeout, int nanos) 다른 쓰레드가 notify()나 notifyAll()을 호출할 때까지 현재 쓰레드를 무한히 또는 지정된 시간(timeout, nanos)동안 기다리게 한다.

참고문헌

https://docs.oracle.com/javase/tutorial/java/TOC.html http://www.tcpschool.com/
https://yadon079.github.io/

Leave a comment