코틀린 실드 클래스

반응형

클래스 상속 개념을 소개하고 하위 클래스를 정의하는 방법을 설명하며, 추상 클래스, 인터페이스, 클래스 위임, 봉인된 클래스를 통해 클래스 계층 구조를 알아보자.

  • 상속과 오버라이딩
  • 타입 검사와 캐스트
  • 추상 클래스
  • 인터페이스
  • 봉인된 클래스
  • 위임

8.1 상속

자바와 마찬가지로 코틀린 클래스는 단일 상속만을 지원한다.

8.1.1 하위 클래스 선언

클래스를 정의하면서 주생성자 뒤에 콜론(

:

)으로 표기한다.

  • 자바의 클래스와 메서드는 default로 상속에 열려있고, 상속을 금지하려면 final을 명시
  • 코틀린은 default로 final 이고, 상속을 허용하려면 클래스 앞에 open 지시어 선언
open class Vehicle {
  var currentSpeed = 0

  fun start() {
    println("I'm moving")
  }

  fun stop() {
    println("Stopped")
  }
}

open class FlyingVehicle : Vehicle() {    // Vehicle는 open 지시어를 통해 상속에 열려 있음
  fun takeOff() {
    println("Take off")
  }

  fun land() {
    println("Landed")
  }
}

class Aircraft(val seats: Int) : FlyingVehicle()

// error: this type is final, so it cannot be inherited from
class Airbus(seats: Int) : Aircraft(seats) // Aircraft는 default final 이기에 상속에 대해 닫혀 있음

하위 클래스 인스턴스는 상위 클래스의 멤버를 모두 상속한다.

val aircraft = Aircraft(100)
val vehicle: Vehicle = aircraft
vehicle.start()
vehicle.stop()
aircraft.start()
aircraft.takeOff()
aircraft.land()
aircraft.stop()
println(aircraft.seats)

data 클래스는 항상 final 이기에 open 선언이 불가하다.

// error: modifier 'open' is incompatible with 'data'
open data class Person(val name: String, val age: Int)

인라인 클래스는 상속 및 상위 클래스 역할 불가하다.

class MyBase
// error: inline classes can be only final
open value class MyString(val value: String)
// error: inline class cannot extend classes
value class MyStringInherited(val value: String): MyBase()

객체(동반 객체 포함)는 클래스 상속이 가능하다.

open class Person(val name: String, val age: Int) {
    companion object : Person("Unknown", 0)
}

object JohnDoe : Person("John Doe", 30)

상위 클래스의 open으로 지정된 멤버를 하위 클래스에서 오버라이드 가능하고,

자바와 차이점은 구현하는 메서드 앞에 override 지시어를 붙여야 함

open class Vehicle2 {
  open fun start() {
    println("I’m moving")
  }

  fun stop() {             // final 이기에 상속만 가능하고 하위 메서드에서 오버라이드 불가
    println("Stopped")
  }
}

class Car : Vehicle2() {
  override fun start() {
    println("I'm riding")
  }
}

class Boat : Vehicle2() {
  override fun start() {
    println("I'm sailing")
  }
}

오버라이드하는 멤버를 final로 선언하면 더 이상 하위 클래스에서 오버라이드 불가하다.

open class Vehicle6 {
  open fun start() {
    println("I’m moving")
  }
}

open class Car5 : Vehicle6() {
  final override fun start() {
    println("I'm riding a car")
  }
}

// Car5 클래스에서 start 멤버 함수를 final로 선언했기 때문에 Car5클래스를 오버라이드할 수 없음
class Bus : Car5() {
  // error: 'start' in 'Car' is final and cannot be overridden
  override fun start() {
    println("I'm riding a bus")
  }
}

프로퍼티 오버라이드는 본문 구현 외에 주생성자 파라미터 선언으로 오버라이드 가능하다.

open class Entity {
  open val name: String get() = ""
}

class Person2(override val name: String) : Entity()

불변 프로퍼티를 가변 프로퍼티로 오바라이드할 수 있다.

(단, 꼭 필요한 경우가 아니라면 상위 클래스의 선언한 값을 그대로 사용)

open class Entity2 {
  open val name: String get() = ""
}

class Person3() : Entity2() {
  override var name: String = ""   // val -> var 
}

자바와 마찬가지로 코틀린도 멤버의 영역을 하위 클래스의 영역으로만 제한하는 특별한 접근 변경자(protected)를 제공한다.

  • Java: 동일한 패키지 안에서 속한 코드에서 멤버 접근을 허용
  • Kotlin: 동일 클래스 및 그 클래스를 상속한 하위 클래스에서만 멤버 접근이 허용
open class Vehicle7 {
  protected open fun onStart() { }
  fun start() {
    println("Starting up...")
    onStart()
  }
}

class Car6 : Vehicle7() {
  override fun onStart() {
    println("It's a car")
  }
}

fun main3() {
  val car = Car6()
  car.start()    // Ok
  // error: cannot access 'onStart': it is protected in 'Car'
  // car.onStart()  상속한 하위 클래스에서만 접근 가능
}

함수나 프로퍼티를 오버라이드한 상위 클래스를 재사용해야 하는 경우 super 키워드를 사용한다.

open class Vehicle {
    open fun start(): String? = "I'm moving"
}

open class Car : Vehicle() {
    override fun start() = super.start() + " in a car" 
}

fun main() {
    println(Car().start())
}

// I'm moving in a car

8.1.2 하위 클래스 초기화

특정 클래스의 인스턴스 생성 시 자신의 상위 클래스 생성자를 호출하며 최상위 클래스에 이를 때까지 연쇄적으로 일어나고, 최상위클래스로 부터 하위 클래스 순서로 초기화가 진행된다.

open class Vehicle {
  init {
    println("Initializing Vehicle")
  }
}

open class Car : Vehicle() {
  init {
    println("Initializing Car")
  }
}

class Truck : Car() {
  init {
    println("Initializing Truck")
  }
}

fun main() {
  Truck()
}

// result
Initializing Vehicle
Initializing Car
Initializing Truck

위임 호출(delegating call)을 사용하면 인자를 상위 클래스의 일반생성자 및 주,부생성자에게 전달할 수 있다.

// 일반생성자에 전달
open class Person(val name: String, val age: Int)

class Student(name: String, age: Int, val university: String) :
    Person(name, age)

fun main() {
  Student("Euan Reynolds", 25, "MIT")
}

// 주, 부생성자에 전달
open class Person {
  val name: String
  val age: Int

  constructor(name: String, age: Int) {
    this.name = name
    this.age = age
  }
}

class Student(name: String, age: Int, val university: String) :
    Person(name, age)

Student 클래스에서 부생성자를 사용할 경우 위임 호출할 클래스를 생성자 뒤에

( )

괄호없이 작성한다.

open class Person(val name: String, val age: Int)

class Student : Person {
  val university: String

  constructor(name: String, age: Int, university: String) :
    super(name, age) {  // super 키워드는 부생성자가 상위 클래스의 생성자를 위임 호출하는것을 컴파일러에게 전달
    this.university = university
  }
}

상위 클래스의 초기화 코드가 호출되고 하위 클래스에서 오버라이드한 함수가 아직 초기화되지 않은 상태일 경우 this 누출(leaking this) 문제가 발생할 수 있다.

open class Person10(val name: String, val age: Int) {
  open fun showInfo() {
      println("$name, $age")
  }

  init {
      showInfo()  // 잠재적인 위험 요소
  }
}

class Student7(
  name: String,
  age: Int,
  val university: String
) : Person10(name, age) {
  override fun showInfo() {
      println("$name, $age (student at $university)")
  }

}

fun main() {
  // Euan Reynolds, 25 (student at null)
  Student7("Euan Reynolds", 25, "MIT")
}

8.1.3 타입 검사와 캐스팅

코틀린의 타입 검사와 캐스팅 연산에는 is를 사용한다.

val objects = arrayOf("1", 2, "3", 4)

// objects 는 Any 로 이뤄진 배열에 원소에 String or Int 연산을 바로 할 수 없음
for (obj in objects) {
  println(obj*2) // error: unresolved reference
}

for (obj in objects) {
  println(obj is Int)  // is 연산자를 통해 타입 검사가 가능하다
}

// result
false
true
false
true

null값에 대한 is 연산 처리

  • Java instanceof 와 유사하고 차이점은 null 에 대해 항상 false
  • 연산자 오른쪽에 널이 될 수 있는지 여부에 따라 다르다(스마트 캐스트 지원)
//null이 될 수 없는 타입
println(null is Int)     // false
//null이 될 수 있는 타입
println(null is String?) // true 

!is 연산자는 is와 반대인 연산을 제공한다.

val o: Any = ""
println(o !is Int)    // true
println(o !is String) // false

값의 타입을 자동으로 세분화해서 Null이 될 수 없는 타입으로 바꿔준다.

val objects2 = arrayOf("1", 2, "3", 4)

var sum = 0

for (obj in objects2) {
  if (obj is Int) {
    sum += obj // 여기서는 obj의 타입을 `Int`로 세분화한다
  }
}
println(sum) // 6

is/!is 검사를 in/!in처럼 특별한 조건으로 사용할 수 있는 식 내부에서도 스마트 캐스트가 지원된다.

  • 컴파일러는 검사 시점과 사용 시점 사이에 변수가 변경되지 않는다고 확신할 수 있을 때만 스마트 캐스트를 허용한다.
val objects3 = arrayOf("1", 2, "3", 4)
var sum2 = 0

for (obj in objects3) {
  when (obj) {
    is Int -> sum2 += obj            // 여기서 obj는 Int 타입이다
    is String -> sum2 += obj.toInt() // 여기서 obj는 String 타입이다
  }
}
println(sum2) // 10
  • 프로퍼티나 커스텀 게터가 정의된 변수에는 스마트 캐스트 사용이 불가하다. (값이 변경될 수 있다고 예측될 때)
class Holder {
  val o: Any get() = ""   // 게터 통해서 값이 변경될 수 있음
}
fun main9() {
  val o: Any by lazy { 123 }

  if (o is Int) {
    println(o*2)             // error: smart cast to 'Int' is impossible
  }

  val holder = Holder()

  if (holder.o is String) {
    println(holder.o.length) // error: smart cast to 'String' is impossible
  }
}
  • 가변 지역 변수의 경우 런타임 시점에 변수가 변경될지 예측할 수 없을 경우 스마트 캐스트 지원이 불가하다.
fun main() {
  var o: Any = 123    // 위임이 없는 불변 지역변수는 가능
  if (o is Int) {
    println(o + 1)    // Ok: Int로 스마트 캐스트
    o = ""
    println(o.length) // Ok: String으로 스마트 캐스트
  }
  if (o is String) {
    val f = { o = 123 }  // 람다 안에서 변수를 변경할 경우 지원 불가
    //  println(o.length) // error: smart cast to 'String' is impossible
  }
}
  • 스마트 캐스트를 쓸 수 없는 경우 (as와 as?)명시적인 연산자를 사용해 값의 타입을 강제로 변환할 수 있다.
val o: Any = 123
println((o as Int) + 1)              // 124
println((o as? Int)!! + 1)           // 124
println((o as? String ?: "").length) // 0       변환하려는 타입과 일치하지 않치만 안전한 as?
//println((o as String).length)      // java.lang.ClassCastException  실제 타입과 일치하지 않아 예외

8.1.4 공통 메서드

Kotlin.Any 클래스는 코틀린 클래스 계층 구조의 루트로 모든 클래스는 직간적접으로 상속하고 기본 연산자를 제공한다.

open class Any {
    public open operator fun equals(other: Any?): Boolean // 구조적 동등성 (== 나 !=)
    public open fun hashCode(): Int // HashSet, HashMap
    public open fun toString(): String // String 변환
}

디폴트로 정의된 모든 클래스에는 Any에서 상속받은 참조 동등성만 구현하기에 두 인스턴스가 같은 객체로 간주되지 않는다.

fun main() {
  val addresses = arrayOf(
    Address("London", "Ivy Lane", "8A"),
    Address("New York", "Kingsway West", "11/B"),
    Address("Sydney", "North Road", "129")
  )

  // -1
  println(addresses.indexOf(Address("Sydney", "North Road", "129")))
}

equal() 메서드를 오버라이드하여 값 비교 (동등성) 문제를 해결할 수 있다.

class Address(
  val city: String,
  val street: String,
  val house: String
) {
  // Address에 정의한 equals 함수
  override fun equals(other: Any?): Boolean {
    if (other !is Address) return false
    return city == other.city &&
      street == other.street &&
      house == other.house
  }
}

fun main() {
    // 2
    println(addresses.indexOf(Address("Sydney", "North Road", "129")))  
}

일반 값에 대한 동등성 비교는 ==와 !=, 참조 동등성은 === 사용하여 비교한다.

  • 코틀린에서 ==는 내부적으로 equals 호출 (=자바 equals)
  • 자바 주소 값 비교인 == 와 코틀린의 === 동일한 역할
val addr1 = Address2("London", "Ivy Lane", "8A")
val addr2 = addr1                                // 같은 인스턴스
val addr3 = Address2("London", "Ivy Lane", "8A") // 다른 인스턴스지만, 동등함
println(addr1 === addr2) // true
println(addr1 == addr2)  // true
println(addr1 === addr3) // false

프로퍼티는 각자의 equals()와 hashCode() 오버라이드하여 해시와 동등성을 계산 할 수 있다.(배열은 타입은 예외)

class Address(
  val city: String,
  val street: String,
  val house: String
) {
  // Address에 정의한 equals 함수
  override fun equals(other: Any?): Boolean {
    if (other !is Address2) return false
    return city == other.city &&
      street == other.street &&
      house == other.house
  }

  // 위 equals와 호환이 되는 해시코드 정의
  override fun hashCode(): Int {
    var result = city.hashCode()
    result = 31 * result + street.hashCode()
    result = 31 * result + house.hashCode()
    return result
  }
}

fun main() {
 val addr1 = Address("London", "Ivy Lane", "8A")
 val addr3 = Address("London", "Ivy Lane", "8A") 

 // true
 println(addr1 == addr3)
}

8.2 추상 클래스와 인터페이스

8.2.1 추상 클래스와 추상 멤버

자바와 동일하게 추상 클래스를 지원하고, abstract라는 키워드를 붙여야 한다.

abstract class Entity(val name: String)

// Ok: 하위 클래스에서 위임 호출
class Person(name: String, val age: Int) : Entity(name)

// 인스턴스 생성 불가하고 부모 클래스로만 사용
// error: cannot create an instance of an abstract class    
val entity = Entity("Unknown")

추상 클래스는 추상 멤버를 정의할 수 있다.

import kotlin.math.PI

abstract class Shape {
  abstract val width: Double
  abstract val height: Double
  abstract fun area(): Double
}

class Circle(val radius: Double) : Shape() {
  val diameter get() = 2*radius
  override val width get() = diameter    // 추상 클래스 멤버를 오버라이드해서 구현
  override val height get() = diameter
  override fun area() = PI*radius*radius
}

class Rectangle(
  override val width: Double,
  override val height: Double
) : Shape() {
  override fun area() = width*height
}

fun Shape.print() {
  println("Bounds: $width*$height, area: ${area()}")
}

fun main() {
  // Bounds: 20.0*20.0, area: 314.1592653589793
  Circle(10.0).print()

  // Bounds: 3.0*5.0, area: 15.0
  Rectangle(3.0, 5.0).print()
}

추상 클래스 제약사항은 다음과 같다.

  • 추상 프로퍼티를 초기화할 수 없고 명시적인 접근자나 by 절을 추가할 수 없다.
  • 추상 함수에는 본문이 없어야 한다.
  • 추상 프로퍼티와 함수 모두 명시적으로 반환 타입을 적어야 한다.
  • 추상 멤버는 암시적으로 열려 있어 open 생략 가능하다.

8.2.2 인터페이스

코틀린 인터페이스 개념은 자바의 인터페이스와 비슷하고,자바 8에 디폴트 메서드가 도입된 이후로 더 많이 비슷해 졌다.

  • default 키워드 없이 메서드 구현
  • 프로퍼티, 함수 구현 추가 가능
interface Vehicle9 {
  val currentSpeed: Int  // 디펄트가 추상 멤버이고, abstract 지시어는 생략 가능
  fun move()
  fun stop()
}

인터페이스는 클래스나 다른 인터페이스의 상위 타입이 될 수 있다.

  • 코틀린에서는 모든 상속(클래스, 인터페이스)을 똑같은 기호 ( : )를 사용해 표시한다.
interface FlyingVehicle2 : Vehicle9 {
  val currentHeight: Int
  fun takeOff()
  fun land()
}

class Car8 : Vehicle9 {           
  override var currentSpeed = 0    // Vehicle9에 추상 멤버에 대한 구현을 제공해야 한다
      private set

  override fun move() {
    println("Riding...")
    currentSpeed = 50
  }

  override fun stop() {
    println("Stopped")
    currentSpeed = 0
  }
}

class Aircraft2 : FlyingVehicle2 {
  override var currentSpeed = 0    // FlyingVehicle2, Vehicle9에 추상 멤버에 대한 구현을 제공해야 한다
      private set

  override var currentHeight = 0
      private set

  override fun move() {
    println("Taxiing...")
    currentSpeed = 50
  }

  override fun stop() {
    println("Stopped")
    currentSpeed = 0
  }

  override fun takeOff() {
    println("Taking off...")
    currentSpeed = 500
    currentHeight = 5000
  }

  override fun land() {
    println("Landed")
    currentSpeed = 50
    currentHeight = 0
  }
}

인터페이스 안의 함수와 프로퍼티에 구현을 추가할 수도 있다.

interface Vehicle10 {
  val currentSpeed: Int

  val isMoving get() = currentSpeed != 0

  fun move()            // 암시적으로 열려 있고, final로 정의하면 컴파일 오류 발생

  // error: modifier 'final' is not applicable inside 'interface'
  final fun move() {}

  fun stop()

  fun report() {
    println(if (isMoving) "Moving at $currentSpeed" else "Still")
  }
}

인터페이스 내부에는 초기화 코드나 위임이 붙은 프로퍼티는 금지된다.

interface Vehicle13 {
  // error: property initializers are not allowed in interfaces
  val currentSpeed = 0
  // error: delegated properties are not allowed in interfaces
  val maxSpeed by lazy { 100 }
}

추상 클래스와 달리 인터페이스에 대한 생성자는 금지된다.

// error: property initializers are not allowed in interface
interface Person(val name: String)

interface Vehicle {
    // error: delegated properties are not allowed in interface
    constructor(name: String)
}

한 타입이 동일한 시그니처를 가지는 멤버가 들어있는 다른 인터페이스를 둘 이상 상속할 때는 super<상위타입>.메서드 호출을 사용한다.

interface Car11 {
  fun move()
}

interface Ship2 {
  fun move()
}

class Amphibia : Car11, Ship2 {
  override fun move() {
    println("I'm moving")
  }
}

interface Car12 {
  fun move(){
    println("I'm riding")
  }
}

interface Ship3 {
  fun move()
}

class Amphibia2 : Car12, Ship3 {
  override fun move() {
    super.move() // Car에서 상속받은 메서드를 호출하는 문제가 생김

    super<Car11>.move()  // Car11에서 상속받은 메서드를 호출
    super<Ship2>.move()
  }
}

fun main() {
  Amphibia2().move() // I'm riding
}

8.2.3 봉인된 클래스와 인터페이스

Enum 클래스는 미리 정의된 상수 집합을 표현하여 이외의 값이 들어오는걸 막을 수 있다.

enum class Result {
  SUCCESS, ERROR
}

fun runComputation(): Result {
  try {
    println("Input a int:")
    val a = readLine()?.toInt() ?: return Result.ERROR
    println("Input a int:")
    val b = readLine()?.toInt() ?: return Result.ERROR

    println("Sum: ${a + b}")

    return Result.SUCCESS
  } catch (e: NumberFormatException) {
    return Result.ERROR
  }
}

fun main() {
  val message = when (runComputation()) {   // when식을 통해 미리 정의된 상수 처리 및 이외의 값이 들어올 수 없는 것을 컴파일 단에서 감지
    Result.SUCCESS -> "Completed successfully"
    Result.ERROR -> "Error!"
  }

  println(message)
}

하지만 특정 케이스의 경우 각 종류별로 애트리뷰트가 다를 수 있어, 추상 클래스를 사용하여 성공 및 하위 클래스의 특정 케이스를 처리할 수 있지만, Enum 처럼 type의 제한을 할 수 없다.

abstract class Result2 {
  class Success(val value: Any) : Result2() {
    fun showResult() {
      println(value)
    }
  }

  class Error(val message: String) : Result2() {
    fun throwException() {
      throw Exception(message)            // type 을 다양하게 처리 가능함
    }
  }
}

fun runComputation2(): Result2 {
  try {
    println("Input a int:")
    val a = readLine()?.toInt()
        ?: return Result2.Error("Missing first argument")
    println("Input a int:")    
    val b = readLine()?.toInt()
        ?: return Result2.Error("Missing second argument")

    return Result2.Success(a + b)
  } catch (e: NumberFormatException) {
    return Result2.Error(e.message ?: "Invalid input")
  }
}

fun main() {
  val message = when (val result = runComputation2()) {
    is Result2.Success -> "Completed successfully: ${result.value}"
    is Result2.Error -> "Error: ${result.message}"
    else -> return     // result 결과를 제한할 수 없어 else 절이 필요함 
  }
  println(message)
}

코틀린은 (sealed)봉인된 클래스나 인터페이스(코틀린 1.5 이상)를 통해 이런 문제를 극복할 수 있다.

  • 상속하는 클래스는 내포된 클래스 or 객체로 정의, 같은 파일 내에 최상위 클래스로 정의돼야만 한다.
  • 이 밖에 영역에서는 봉인된 클래스 상속 불가하다. (final 클래스와 동일 효과)
  • 직접 인스턴스를 만들 수 없어 기본적으로 추상 클래스 이다.
sealed class Result {  // Result 클래스는 Success 와 Error로 제한했다
    class Success(val value: Any) : Result() {...}
    class Error(val message: String) : Result() {...}
}

Enum과 마찬가지로 불필요한 else를 사용 안해도 되는 빠진 부분이 없는 when 형태를 지원한다.

// compile 시점에 Result의 하위 클래스는 Success와 Error만 존재한다고 인식
val message = when (val result = runComputation()) {
    is Result.Success -> "Completed successfully: ${result.value}"
  is Result.Error -> "Error: ${result.message}"
}

// 봉인된 클래스 확장시 when 구문에 실수로 코드를 누락하는일이 발생하지 않는다
class Confirm(val message: String) : Result() {...}

상속 제한은 봉인된 클래스를 직접 상속한 클래스에 대해서만 성립한다.

// Result.kt
sealed class Result {  // Result 클래스는 Success 와 Error로 제한했다
    class Success(val value: Any) : Result()
    open class Error(val message: String) : Result()
}

// Util.kt
class FatalError(message: String): Result.Error(message)

데이터 클래스가 봉인된 클래스 계층에 속할 수도 있다.

sealed class Expr

data class Const(val num: Int): Expr()   // 봉인된 클래스의 하위 계층으로 사용
data class Neg(val operand: Expr): Expr()

봉인된 클래스를 객체로 구현할 수도 있다.

sealed class Result {
    object Completed: Result()
    class ValueProduced(val value: Any) : Result()
    class Error(val message: String) : Result()
}

8.2.4 위임

기존 클래스의 확장이나 변경이 어려울 경우 위임 패턴을 사용할 수 있다.

interface PersonData {
  val name: String
  val age: Int
}

open class Person17(
  override val name: String,
  override val age: Int
): PersonData

data class Book(val title: String, val author: PersonData) {
  override fun toString() = "'$title' by ${author.name}"
}

fun main() {
  val valWatts = Person17("Val Watts", 30)
  val introKotlin = Book("Introduction to Kotlin", valWatts)

  println(introKotlin) // 'Introduction to Kotlin' by Val Watts
}

// 메서드나 프로퍼티를 다른 객체에 전달하기 위해 작성해야하는 준비 코드가 너무 많은 것이 단점
class Alias(
  private val realIdentity: PersonData,
  private val newIdentity: PersonData
) :PersonData {
  override val name: String        // 인터페이스의 프로퍼티를 오버라이드한 커스텀 게터 작성
    get() = newIdentity.name

  override val age: Int
    get() = newIdentity.age
}

fun main21() {
  val valWatts = Person17("Val Watts", 30)
  val johnDoe = Alias(valWatts, Person17("John Doe", 25))
  val introJava = Book("Introduction to Java", johnDoe)

  println(introJava) // 'Introduction to Java' by John Doe
}

코틀린은 위와 같은 위임을 처리하는 기능을 내장하고 있다.

  • 상위 인터페이스 이름 바로 뒤 (by) 키워드 작성 후 위임할 인스턴스를 작성한다
class Alias(
  private val realIdentity: PersonData,
  private val newIdentity: PersonData
): PersonData by newIdentity // newIdentity 인스턴스에 있는 이름으로 메서드가 구현된다

구현을 바꾸고 싶은 경우 직접 멤버를 오버라이드할 수 있다.

class Alias(
  private val realIdentity: PersonData,
  private val newIdentity: PersonData
): PersonData by newIdentity {
    override val age: Int get() = realIdentity.age
}

클래스는 인터페이스 멤버를 구현할 때만 위임을 쓸 수 있다.

open class Person(  
  override val name: String,
  override val age: Int
)

class Alias(
  private val realIdentity: PersonData,
  private val newIdentity: PersonData
): Person by newIdentity  // error: only interface can be delegated to   // 클래스는 위임 불가

클래스 위임은 번거로운 준비 코드없이 객체 합성과 상속의 이점을 살릴 수 있다.

반응형

+ Recent posts

반응형