Lined Notebook

[자바 개발자를 위한 코틀린 입문] 상속 다루기

by dev.hardy

코틀린에서 상속을 다루는 방법

인프런에서 최태현 지식 공유자님의
자바 개발자를 위한 코틀린 입문을 수강하며 정리한 내용 입니다.
하단에 강의 링크 있습니다.

추상 클래스

예) Animal이란 추상클래스를 구현한 Cat, Penguin

1) Animal

abstract class Animal(
    protected val species: String,
    protected val legCount: Int,
) {
    abstract fun move()
}
  • extends를 사용하지 않고 : 를 사용한다.
  • 상위 클래스 상속을 구현할 때 생성자를 반드시 호출해야 한다.
  • 추상 멤버가 아니면 기본적으로 오버라이드가 불가 → open을 사용해주어야 함
  • convention 중에서 타입을 지정할때는 변수명: 타입 이라 쓰고, 상속을 할때는 : 상속받는타입 한칸 앞에 띄우고 사용한다.
  • species를 받아 상속받는 부분에서 Animal 생성자를 호출해준다.
  • 자바와는 달리 오버라이드 메소드를 override 지시어를 사용한다.
class Cat(
    species: String
) : Animal(species, 4) {
    override fun move() {
        println("고양이가 움직입니다.")
    }
}
  • 코틀린은 자바와 다르게 프로퍼티를 override 할 때 무조건 open 을 붙어주어야 한다.
    • 즉, 추상 프로퍼티가 아니라면, 상속받을때 open 을 붙어야함
  • 추상클래스에서 자동으로 만들어진 getter를 override
  • 상위클래스에 관한 키워드는 super 이다.
class Penguin(
    species: String
) : Animal(species, 2) {
    private val wingCount: Int = 2

    override fun move() {
        println("펭귄이 움직입니다.")
    }

    override val legCount: Int
        get() = super.legCount + this.wingCount

}

→ 자바와 코틀린 모두 추상클래스는 인스턴스화 하지 못한다

인터페이스

예) Flyable과 Swimable을 구현한 Penguin

  • 자바와 달리 default 를 사용하지 않더라도 인터페이스에서 기본 메소드를 만들 수 있다.
  • 코틀린에서도 인터페이스에 추상메소드를 만들 수 있다.
  • 인터페이스 구현도 : 사용
  • 특정 상위 인터페이스의 함수를 override하기 (중복되는 인터페이스를 특정할 때)
    • 자바: 인터페이스타입.super.메소드
    • 코틀린: super<인터페이스타입>.메소드
  • 자바와 마찬가지로 인터페이스를 인스턴스화 할 수 없다.

1) Flyable, Swimable

interface Flyable {
    fun act(){
        println("파닥 파닥")
    }
}

interface Swimable {
    fun act(){
        println("첨벙 첨벙")
    }
}

2) Animal 상속, Flyable, Swimable 구현을 하는 Penguin

class Penguin(
    species: String
) : Animal(species, 2), Swimable, Flyable {
    private val wingCount: Int = 2

    override fun move() {
        println("펭귄이 움직입니다.")
    }

    override val legCount: Int
        get() = super.legCount + this.wingCount

    override fun act() {
        super<Swimable>.act()
        super<Flyable>.act()
    }
}
  • 코틀린에서는 backing field가 없는 프로퍼티를 Interface에 만들 수 있다.
interface Swimable {

    val swimAbility: Int // (1)
            get() = 7 // (3)

    fun act(){
        println("첨벙 첨벙")
    }
}
class Penguin(
    species: String
) : Animal(species, 2), Swimable, Flyable {
    private val wingCount: Int = 2

    override fun move() {
        println("펭귄이 움직입니다.")
    }

    override val legCount: Int
        get() = super.legCount + this.wingCount

    override fun act() {
        super<Swimable>.act()
        super<Flyable>.act()
    }

    override val swimAbility: Int // 추가 (2)
        get() = 7
}

(1) 이렇게 인터페이스에 val로 정의한 프로퍼티는 구현체에서 getter를 정의할 것을 기대하고 있다.

(2) 구현체에서 직접 정의를 할 수 있다. (backing field 없이)

(3) 인터페이스에서도 default 로 구현해주는 방법도 있다.

🚩 프로퍼티는 사실은 field라는 것도 있지만 getter라는 것도 있기 때문에 어떻게 보면 getter에 대한 default 메소드나 getter에 대한 추상 메소드를 인터페이스에 만드는 거라 이렇게 backing field 없는 프로퍼티를 인터페이스에 만들 수 있다.

클래스를 상속받을때 주의할 점

fun main() {
    Derived(300) // (1)
}

open class Base(
    open val number: Int = 100
) {
    init {
        println("Base Class")
        println(number)
    }
}

class Derived(
    override val number: Int
) : Base(number) {
    init {
        println("Derived Class")
    }
}

/**
Base Class
0
Derived Class
*/

(1) 300이라는 값을 주고 Derived를 인스턴스화 했다.

(2) 100도 아니고 300도 아니고 출력 결과에는 0이 나온다.

Accessing non-final property number in constructor 컴파일러는 경고를 주고 있다.

→ 즉, 상위 클래스 생성자가 실행되는 동안 하위 클래스의 프로퍼티 즉 이 Derived를 인스턴스화 한다는 말은 Derived에 있는 number에 값을 집어 넣는다는 말인데 이때 상위 클래스에서 number를 호출하게 되면 하위 클래스에 있는 number를 가져오는 것인데 아직 상위 클래스에 constructor가 먼저 실행된 단계라서 하위 클래스에 number라는 값에 초기화가 이루어지지 않은 것이다. 그 상태에서 먼저 하위 클래스의 number에 접근을 하는 거라 100도 아니고 300이 아닌 int 초기값인 0이 나오는 것이다.

그래서 상위 클래스에 constructor와 init 블락에서는 하위 클래스의 field에 접근하면 안된다라는 말이다.

정확히는 final 아닌 프로퍼티에 접근하면 안되는 것이다.

하위 클래스에서 override 하고 있는 프로퍼티에 접근하면 안된다는 것이다.

상위 클래스를 설계할 때 생성자 또는 초기화 블록에 사용되는 프로퍼티에는 open을 피해야 한다.

→ 상위 클래스의 생성자 또는 초기화 블록에서 open 프로퍼티를 사용하면 얘기치 못한 버그가 발생 가능

상속 관련 키워드 4가지 정리

  1. final: override를 할 수 없게 한다. default로 보이지 않게 존재
  2. open: override를 할 수 있게 열어준다. (해도되고 안해도 된다.)
  3. abstract: 반드시 override를 해야 한다.
  4. override: 상위 타입을 override 하고 있다.

Reference:
자바 개발자를 위한 코틀린 입문

블로그의 정보

개발자 하디 : 기록저장소

dev.hardy

활동하기