본문 바로가기
Java & Kotlin/Kotlin

[Kotlin] 상속을 다루는 방법

by heekng 2022. 7. 6.
반응형

상속을 다루는 방법

추상 클래스

Java

public abstract class Animal {

    protected final String species;
    protected final int legCount;

    public Animal(String species, int legCount) {
        this.species = species;
        this.legCount = legCount;
    }

    abstract public void move();

    public String getSpecies() {
        return species;
    }

    public int getLegCount() {
        return legCount;
    }
}

public class JavaCat extends Animal{

    public JavaCat(String species) {
        super(species, 4);
    }

    @Override
    public void move() {
        System.out.println("고양이가 걷는다.");
    }

}

Kotlin

abstract class Animal(
        protected val species: String,
        protected open val legCount: Int
) {

    abstract fun move()

}

class Cat(species: String) : Animal(species, 4) {

    override fun move() {
        println("고양이가 걷는다.")
    }

}
  • 코틀린에서 상속은 extends 키워드를 사용하지 않고 :를 사용한다.
    • 변수의 타입을 지정하는 것과 다르게 앞뒤로 띄어쓰기를 해야 한다.
  • 추상 클래스를 상속받을 시 상위 클래스의 생성자를 바로 호출해야 한다.
  • override를 필수적으로 붙여 함수를 상속받는다.
  • 코틀린에서 추상 멤버가 아닌 멤버를 override할 때에는 무조건 open을 붙여주어야 한다.
  • override 키워드와 custom getter을 사용해서 추상클래스에서 만들어진 프로퍼티를 override할 수 있다.

인터페이스

Java

public interface JavaFlyable {

    default void act() {
        System.out.println("파닥 파닥");
    }

}

public interface JavaSwimable {

    default void act() {
        System.out.println("어푸어푸");
    }

}

public class JavaPenguin extends Animal implements JavaFlyable, JavaSwimable{

    private final int wingCount;

    public JavaPenguin(String species) {
        super(species, 2);
        this.wingCount = 2;
    }

    @Override
    public void move() {
        System.out.println("펭귄이 움직인다.");
    }

    @Override
    public int getLegCount() {
        return super.getLegCount() + this.wingCount;
    }

    @Override
    public void act() {
        JavaSwimable.super.act();
        JavaFlyable.super.act();
    }

}

Kotlin

interface Flyable {

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

}

interface Swimable {

    val swimAbility: Int

    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
        get() = 3

}
  • 인터페이스 구현도 implement가 아닌 :를 사용한다.
  • 코틀린의 인터페이스에서 default 메서드를 구현할 때 default 키워드 없이 메서드 구현이 가능하다.
  • 코틀린에서도 추상 메서드를 만들 수 있다.
  • 중복되는 인터페이스를 특정할 때에는 super<타입>.함수를 사용한다.
  • 코틀린에서는 backing field가 없는 프로퍼티를 interface 안에 만들 수 없다.
    • 인터페이스에 필드를 생성하였다면 해당 인터페이스에서 get()을 구현하거나, 상속받은 구현체에서 get()override 해주어야 한다.
    • 위 코드에서 swimAbility의 경우 인터페이스에서 get을 구현하지 않았기 때문에 구현한 Penguin에서 swimAbility를 override하여 구현했다.

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

Kotlin

fun main() {
    Derived(32)
}

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")
    }
}

  • 위 코드를 보면 Derived를 인스턴스화 할 때 Base의 생성자를 먼저 부른 후, init 실행, 그 다음에 Derived의 init을 실행한다.
  • 문제는 Base의 println(number)에 있다.
  • number 프로퍼티는 Derived에서 상속받았기 때문에 Base의 init에서 사용한 number은 자식인 Derived의 number을 이용한다.
  • 하지만 Base의 생성자가 먼저 호출되어 Base.init에 있는 number은 아직 초기화되지 않은 number이다.
  • 때문에 0이 출력된다.
  • 상위 클래스를 설계할 때 생성자 또는 초기화 블록에 사용되는 프로퍼티에는 open을 피해야 한다.

상속 관련 지시어

  • final: override를 할 수 없게 한다. default로 보이지 않고 존재한다.
  • open: override를 열어준다.
  • abstract: 반드시 override 해야 한다.
  • override: 상위 타입을 오버라이드 하고 있다.
반응형