-
[코틀린] 클래스와 상속코틀린 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