ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [코틀린] 클래스와 상속
    코틀린 2022. 3. 13. 22:51

    1. 인터페이스와 추상클래스

    인터페이스

    코틀린 인터페이스는 아래처럼 작성한다.

    interface Remote {
        fun up()
        fun down()
        fun doubleUp() {
            up()
            up()
        }
    }
    

    up, down은 추상 메소드로 상속받는 클래스에서 반드시 구현을 해야한다. doubleUp은 상속받는 클래스에서 추가적인 작업을 하지 않아도 된다. 

    클래스에서 인터페이스를 상속받아서 추상메소드를 구현해서 사용할 수 있다.

    class TV {
        var volume = 0
    }
    
    class TVRemote(val tv: TV): Remote { // :을 이용하여 상속받음
        override fun up() { // 추상메소드를 override해야함
            tv.volume++;
        }
    
        override fun down() {
            tv.volume--;
        }
    }
    
    val tv = TV()
    val tvRemote = TVRemote(tv)
    tvRemote.up()
    println(tv.volume) // 1 출력
    tvRemote.doubleUp()
    println(tv.volume) // 3 출력
    tvRemote.down()
    println(tv.volume) // 2 출력

    코틀린에서는 인터페이스 자체도 static 메소드를 가질 수 있다. companion object를 인터페이스 내에 구현할 수 있어 companion object내에 static 메소드들을 정의하면 된다.

    interface Remote {
        fun up()
        fun down()
        fun doubleUp() {
            up()
            up()
        }
        companion object {
            fun combine(first: Remote, second: Remote): Remote = object: Remote {
                override fun up() {
                    first.up()
                    second.up()
                }
    
                override fun down() {
                    first.down()
                    second.down()
                }
            }
        }
    }
    val tv1 = TV()
    val tv2 = TV()
    val tv1AndTv2 = Remote.combine(TVRemote(tv1), TVRemote(tv2))
    tv1AndTv2.up()
    println("${tv1.volume} ${tv2.volume}") // 1 1 출력됨

     

    추상클래스

    추상 클래스는 abstract class를 이용해서 정의할 수 있다.

    abstract class CellularPhone(val name: String) {
        fun call() {
            println("call")
        }
        abstract fun ring()
    }
    
    class IPhone(name: String): CellularPhone(name) {
        override fun ring() {
            println("Iphone ringing")
        }
    }
    
    class Galaxy(name: String): CellularPhone(name) {
        override fun ring() {
            println("Galaxy ringing")
        }
    }
    
    val iphone = IPhone("My iphone")
    val galaxy = Galaxy("My galaxy")
    println("${iphone.name} ${galaxy.name}") // My iphone My galaxy
    iphone.call() // call
    galaxy.call() // call
    iphone.ring() // Iphone ringing
    galaxy.ring() // Galaxy ringing

    추상 클래스는 추상메소드와 구현된 메소드를 가질 수 있고 백킹필드도 가지고 있을 수 있다.

    추상클래스와 인터페이스의 차이

    • 추상클래스는 백킹 필드가 있고 인터페이스는 백킹 필드가 없다.
    • 추상클래스는 하나만 상속받을 수 있지만 인터페이스는 여러개를 구현할 수 있다.

     

    2. 중첩 클래스와 내부 클래스

    TV와 TVRemote는 아예 다른 클래스로 분리해서 구현할 수도 있지만 TVRemote를 TV 내부클래스로 구현할 수도 있다. 이 경우 두 클래스를 분리해서 구현할 때에 비해서 한가지 장점이 있는데 TVRemote가 TV의 private 멤버들을 사용할 수 있게된다.

    interface Remote {
        fun up()
        fun down()
        fun doubleUp() {
            up()
            up()
        }
    }
    
    class TV {
        private var volume = 0
        val remote: Remote
        get() = TVRemote()
        inner class TVRemote: Remote {
            override fun up() {
                volume++;
            }
    
            override fun down() {
                volume--;
            }
    
            override fun toString(): String {
                return "Remote: ${this@TV.toString()}"
            }
        }
    
        override fun toString(): String {
            return "Volume: $volume"
        }
    }
    
    val tv = TV()
    val remote = tv.remote
    val remote2 = tv.remote
    println("$remote $remote2") // Remote: Volume: 0 Remote: Volume: 0 출력됨.
    remote.up()
    println(tv) // Volume: 1

    inner class 에서는 this@OuterClass로 외부 클래스를 가리킬 수 있다.

    이 때 생성된 remote와 remote2는 서로 다른 인스턴스이다.

    위와 같은 기능을 익명 내부 클래스를 사용해서 구현할 수도 있다.

    package chapter08
    
    interface Remote {
        fun up()
        fun down()
        fun doubleUp() {
            up()
            up()
        }
    }
    
    class TV {
        private var volume = 0
        val remote: Remote get() = object: Remote {
            override fun up() {
                volume++;
            }
    
            override fun down() {
                volume--;
            }
        }
    
        override fun toString(): String {
            return "Volume: $volume"
        }
    }
    
    val tv = TV()
    val remote = tv.remote
    val remote2 = tv.remote
    println("$remote $remote2") // 출력 chapter08.AnonymousInnerClass$TV$remote$1@5ab29866 chapter08.AnonymousInnerClass$TV$remote$1@74cad577

     

    3. 상속

    코틀린의 클래스들은 디폴트가 final이다. 클래스를 상속 받을 수 있게 하려면 open을 명시해 주어야 한다. 또한 open으로 명시된 클래스의 open으로 명시된 속성만 override 할 수 있다.

    open class Vehicle(val year: Int, open var color: String) {
        open val km = 0
        final override fun toString() = "year : $year, Color: $color, KM: $km"
        fun repaint(newColor: String) {
            color = newColor
        }
    }
    
    open class Car(year: Int, color: String) : Vehicle(year, color) {
        override var km: Int = 0
        set(value) {
            if(value < 1) {
                throw RuntimeException("can`t be < 1")
            }
            field = value
        }
        fun drive(distance:Int) {
            km += distance
        }
    }
    
    val car = Car(2000, "Red")
    car.drive(100)
    println(car.km) // 100 출력
    try { car.drive(-120) } catch(ex: RuntimeException) {println(ex.message)} // can`t be < 1 출력

    Vehicle은 open되어있으므로 Car가 상속 받을 수 있다. val 은 val, var둘 다 오버라이딩 할 수 있지만 var 는 var로만 오버라이딩 할 수 있다. var가 getter, setter를 가지고 있으므로 val 로 오버라이드 할 수는 없다.(자식클래스에서 setter를 지울 수는 없으니까)

     

    4. 씰드 클래스

    sealed class는 같은 파일에 작성된 클래스들로의 확장만 허용된다. sealed class의 생성자는 private이라서 sealed class 자체는 인스턴스를 만들 수 없다. 

    sealed class 는 when과 함께 사용될 때 sealed의 자식 클래스의 경우가 모두 포함되어있으면 else를 작성하지 않아도 된다.

    sealed class Card(val suit: String)
    
    class Ace(suit: String): Card(suit) {
        override fun toString(): String = "ACE of $suit"
    }
    class King(suit: String): Card(suit) {
        override fun toString(): String = "King of $suit"
    }
    class Queen(suit: String): Card(suit) {
        override fun toString(): String = "Queen of $suit"
    }
    import chapter08.Ace
    import chapter08.Card
    import chapter08.King
    import chapter08.Queen
    
    
    val ace = Ace("Diamond")
    val king = King("Spade")
    val queen = Queen("Heart")
    
    println(ace) // ACE of Diamond
    println(king) // King of Spade
    println(queen) // Queen of Heart
    
    fun process(card: Card) = when(card) {
        is Ace -> "ACE"
        is King -> "KING"
        is Queen -> "QUEEN"
    } // else 가 필요 없다.
    
    process(ace) // ACE

     

    5. Enum

    코틀린 Enum 클래스는 기본적으로 아래와 같이 쓸 수 있다.

    enum class Day {
        MON,
        TUE,
        WED,
        THU,
        FRI,
        SAT,
        SUN
    }
    
    fun main() {
        println(Day.MON) // MON
        println(Day.TUE) // TUE
        println(Day.valueOf("MON")) //MON
    }

    Enum 클래스도 상태와 메소드를 가질 수 있다. 이 때는 값이 끝나는 곳에서 세미콜론을 붙여주어야한다.

    enum class Day(val korean: String) {
        MON("월"),
        TUE("화"),
        WED("수"),
        THU("목"),
        FRI("금"),
        SAT("토"),
        SUN("일") {
            override fun toString(): String {
                return "${super.toString()} SUNDAY"
            }
        };
    
        override fun toString(): String {
            return "$name $korean"
        }
    }
    
    fun main() {
        println(Day.MON) // MON 월
        println(Day.TUE) // TUE 화
        println(Day.valueOf("SUN")) // SUN 일 SUNDAY
    }

     

    참고: 다재다능 코틀린 프로그래밍 8장

    '코틀린' 카테고리의 다른 글

    [코틀린] 람다  (0) 2022.03.19
    [코틀린] 델리게이션  (0) 2022.03.16
    [코틀린] 객체와 클래스  (0) 2022.03.13
    [코틀린] 타입 안정성  (0) 2022.03.07
    [코틀린] 컬렉션  (0) 2022.03.07

    댓글

Designed by Tistory.