본문 바로가기
Development/Kotlin

코틀린(Kotlin) 문법 뽀개기 - 4. 클래스와 상속, 그리고 인터페이스

by du.it.ddu 2020. 6. 27.
반응형

코틀린으로 클래스를 작성 해 보자.

코틀린의 클래스는 흔히 하는 Class가 있다. 그 외에도 Data Class, Sealed Class라는 것이 있으나, 이는 나중에 따로 알아보자.

이번 포스팅은 기본적인 클래스와 상속, 그리고 인터페이스에 대해 알아 볼 것이다.


우선 간단한 클래스부터 작성해보자.

class ExampleClass(val a: Int, val b: Int, val c: Int) {
    fun printValues() {
        println("a = $a, b = $b, c = $c")
    }
}

fun main() {
    val instance = ExampleClass(1, 2, 3)
    instance.printValues()
}

이해가 될것이라 생각한다. main 함수에 객체를 생성하고 메서드를 부르는 예시도 적어놓았다.

만약 생성자가 더 필요한 경우에는 어떻게 해야할까? 아래와 같이 수정해본다.

class ExampleClass {
    var a: Int = 0
    var b: Int = 0
    var c: Int = 0

    constructor()
    constructor(a: Int)
    constructor(a: Int, b: Int)
    constructor(a: Int, b: Int, c: Int)

    // constructor(a: Int = 0, b: Int = 0, c: Int = 0)
}

클래스명 뒤에 괄호를 열지 않고 내부에 constructor 메서드를 여러 개 생성한다. 주석으로 처리한 맨 아래의 constructor처럼 디폴트 값을 줄 수도 있다.


이번엔 클래스를 상속해보자. ExampleClass를 상속받아 ExampleClass2를 만들어보자.

class ExampleClass2 : ExampleClass {
    private var d: Int = 0

    constructor() {
        this.d = 0
    }

    constructor(d: Int): super(2, 3) {
        this.d = 2
    }
}

상속은 클래스 명 뒤에 : 를 붙이고 상속할 클래스를 작성한다.

그런데 이 상태에서 위 처럼 작성하면 빨간줄이 뜨며 컴파일에 실패할 것이다.

빨간줄에 커서를 대면 "This type is final, so it cannot be inherited from" 라는 문구를 볼 수 있다.

클래스가 final 타입이면 상속할 수 없다. 그렇다. 코틀린의 클래스는 기본적으로 final이며, 이를 원치 않으면 "open" 키워드를 붙여주어야 한다.

open class ExampleClass {
    ...
}

ExampleClass를 위 처럼 수정하자. 그럼 더 이상 final이 아니며 상속할 수 있다.

ExampleClass2의 생성자들의 뒤에 super 키워드가 붙고 값을 전달하는 것을 볼 수 있는데, 이는 부모 클래스의 생성자를 호출하는 것이다.

호출하지 않을 수 있지만 반대로 특정 생성자를 호출할 수도 있으니 적절하게 사용하면 된다.


이제 추상클래스를 작성해보자. Animal이란 추상클래스를 만들 것이다. 이름을 멤버변수로 가지며 이를 상속하는 클래스는 몇 개의 추상 메서드를 구현해야 한다.

abstract class Animal constructor(val name: String) {
    abstract fun eat(food: String)
    abstract fun sleep()

    override fun toString(): String {
        return "이 동물의 이름은 $name 이다."
    }
}

위와 같이 작성했다. eat(food: String), sleep() 두 가지의 추상 메서드를 제공한다.

이제 이를 상속한 Dog 클래스를 만들어 본다.

class Dog(name: String, private val type: String) : Animal(name) {
    override fun eat(food: String) {
        println("강아지 ${name}은(는) ${food}를 먹었다.")
    }

    override fun sleep() {
        println("강아지 ${name}은(는) 잠을 잔다.")
    }

    override fun toString(): String {
        return "이 강아지의 이름은 $name 이고 종은 $type 이다."
    }
}

Dog는 이름을 생성자로 받아 부모클래스의 생성자에 전달하고 type을 멤버 변수로 갖는다. 위의 ExampleClass와는 달리 Animal에 생성자를 호출하지 않거나 생성자에 name을 전달하지 않으면 에러가 발생한다.


이제 Dog 클래스를 위한 인터페이스를 생성하자. 짖기, 달리기, 물기 정도의 메서드를 제공한다.

interface IDog {
    fun bark(sound: String)
    fun run(speed: Float)
    fun bite(power: Int)
}

위와 같이 작성한다. 이제 Dog 클래스에서 이 인터페이스를 구현한다.

class Dog(name: String, private val type: String) : Animal(name), IDog {
    ....

    override fun bark(sound: String) {
        println("강아지 ${name}은(는) ${sound}소리를 내며 짖었다.")
    }

    override fun run(speed: Float) {
        println("강아지 ${name}은(는) ${speed}속도로 달렸다.")
    }

    override fun bite(power: Int) {
        println("강아지 ${name}은(는) ${power}의 힘으로 물었다.")
    }
}

Animal 클래스 뒤에 콤마를 찍고 IDog를 적어준다. 상속은 단 하나의 클래스만 가능하지만, 인터페이스는 여러 개가 가능하다. 

그리고 인터페이스에서 제공하는 메서드를 오버라이드하여 알맞게 구현한다.


코틀린의 클래스와 상속, 추상클래스, 인터페이스에 대해 알아보았다.

상속과 추상클래스, 인터페이스를 사용하는 이유는 무엇일까?

Java 혹은 객체지향에 대해 공부해 보았다면 익숙하게 접했을 것이다.

이들을 활용하면 깔끔하고 구조적인 설계와 코드 중복의 방지, 다형성 구현 등 이점이 많다.

궁금하다면 객체지향에 대해 공부해보자.

 

반응형