3-2. Kotlin 클래스 상속

2020. 3. 3. 22:40소프트웨어 공학/코딩 공부

본 내용은 kotlinlang.org 공식 문헌과 기타 자료들을 바탕으로 필자가 학습한 내용을 정리한 것입니다.

필자의 허락 없이 글을 상업적 목적으로 수정, 재배포할 수 없습니다. 내용의 오류 지적은 덧글로 받습니다.


최상위 클래스 / Any

모든 클래스는 공통적으로 Any를 상속하는데, 부모 클래스를 정의하지 않아도 마찬가지다.
Any 클래스가 갖는 메서드는 두개지요! 3개입니다.

  • equals() - 두 객체가 서로 일치하는 값인지 조사하는 함수
  • hashCode() - 두 객체의 메모리 주소가 일치하는지 조사하는 함수
  • toString() - 객체 정보를 문자열로 변환하는 함수

상속 / Inheritance

// 부모 클래스
open class Base {
    constructor(x: Int)
    constructor(x: Int, y: Int)
}

// 초기 생성자 있음
class ClassA(x: Int) : Base(x)

// 초기 생성자 없음
class ClassB : Base {
    constructor(x: Int) : super(x)
    constructor(x: Int, y: Int) : super(x, y)
}

위 예제에서 ClassA, ClassB는 Base를 상속하는 자식 클래스이다. 차이점은 ClassA만 초기 생성자[각주:1]를 갖는다.
초기 생성자가 있다면, 반드시 클래스 헤더[각주:2]에서 부모 클래스를 초기화해야 한다.
초기 생성자가 없다면, 생성자에서 super 키워드를 참조하여 부모를 초기화해야 한다.
생성자 뒤에 콜론(:)과 함께 클래스명을 적으면 해당 클래스를 상속하게 되는데, 다중 상속은 불가능하다.[각주:3]
이때 다시 상기할 부분은, 인스턴스를 생성하거나 클래스를 상속할 때 해당 클래스를 초기화하는 것은 소괄호(bracket) 표시이다. e.g. val instance = Class( )


재정의 / Override

부모 클래스의 함수, 프로퍼티가 자식에 의해 재정의 되는 것을 허용하려면 open 키워드[각주:4]를 통해 이를 명시해야 한다.[각주:5] 만약 이를 준수하지 않고 override 한다면 컴파일 오류가 발생한다.
자식 클래스가 부모의 함수나 프로퍼티를 재정의하려면 override 키워드를 사용하자.

 

open class Human {
    open var name = "Human"
    open var age = 0

    open fun introduce() {
        println("Wait, who am I?")
    }
}

// 초기 생성자에서 name 프로퍼티를 재정의함.
class Steve(override var name: String = "Steve") : Human() {
    override val age = 10   // 재정의 오류! var -> val 로 덮어쓸 수 없다.

    override fun introduce() {
        println("My name is $name. I am $age years old.")
    }
}

fun main() {
    Human().introduce() // 출력: Wait, who am I?
    Steve().introduce() // 출력: My name is Steve. I am 10 years old.
}

override 키워드를 붙이면 해당 클래스 멤버는 open 상태가 된다. 하위 클래스가 이것을 재정의하지 못하게 하려면 final 키워드를 사용하자.
프로퍼티를 override 할 때 2가지 규칙이 있다.

  1. 초기 생성자(클래스 헤더)에서 프로퍼티를 재정의할 수 있다.
  2. val을 var로 재정의할 수 있지만 그 반대는 불가능하다. var 프로퍼티는 getter와 setter가 모두 정의되었기 때문에 getter만 정의된 val로 되돌릴 수 없기 때문이다.

 

주의할 점 / Override caution

상속 구조를 가진 클래스를 만들면 초기화는 최상위 클래스부터 시작한다는 사실을 간과하면 안 된다.
만약 상위 클래스가 초기화 도중 open 멤버(함수, 프로퍼티)를 사용하게 되면, 아직 초기화되지 않은 하위 클래스가 재정의하는 값을 건드리는 꼴이 되므로 예기치 못한 문제가 발생할 수 있다.
아래의 상황에서 open 멤버를 사용하는 것은 지양하자:

  • 생성자 내부
  • 프로퍼티 초기화(값 대입)
  • 초기화 블록

 

상위 멤버 호출

위 그림처럼 다중 상속에서 발생하는 문제점이 있다. 자식 클래스가 중복되는 이름의 클래스 멤버를 상속하면, 어떤 것을 선택할지 답이 모호하다는 것이다. 이 경우 해당 멤버를 재정의할 필요가 있는데, 중복되는 멤버 중 한쪽을 선택할 때 super 표기법을 이용할 수 있다: super<Base>

fun main(args: Array<String>) {
    GoodBoy().sound() // 출력: woof
}

interface Pet {
    open fun sound() {
        println("no sound")
    }
}

open class Dog {
    open fun sound() {
        println("woof")
    }
}

class GoodBoy : Dog(), Pet {
    override fun sound() {
        super<Dog>.sound() // Dog의 sound()를 호출
    }
}

 

내부 클래스에서 super 호출

Base를 상속하는 Main 클래스 안에 내부 클래스가 또 정의되어 있다고 하자. 내부 클래스에서 Base를 접근하려면 어떻게 해야 될까? 이렇게 하면 된다: super@Main

 

open class Base {
    open var i = 1
}

class Main : Base() {
    inner class MainInner { // 내부 클래스
        fun test() {
            super@Main.i = 10 // Base에 접근, 프로퍼티 i 수정
        }
    }
}

 


 

  1. 초기 생성자는 클래스 헤더에 위치한다. [본문으로]
  2. 클래스 헤더는 클래스를 정의하는 첫째줄이다. [본문으로]
  3. 인터페이스 다중 상속은 예외적으로 허용된다. [본문으로]
  4. Java의 final 키워드와 정반대라고 생각하면 된다. [본문으로]
  5. 인터페이스 또는 추상 클래스 멤버는 기본적으로 open 상태이다. [본문으로]

'소프트웨어 공학 > 코딩 공부' 카테고리의 다른 글

Java - JSR223 스크립트 API  (0) 2020.03.06
3-3. Kotlin 클래스 추상화  (0) 2020.03.04
3-1. Kotlin 클래스 생성자  (0) 2020.03.03
3. Kotlin 클래스  (0) 2020.03.03
2. Kotlin 자료형  (1) 2020.03.02