kotlin

[kotlin/코틀린] 확장

hrniin 2023. 7. 19. 23:11
728x90

 

확장

클래스 밖에서 함수나 프로퍼티를 멤버인 것처럼 쓸 수 있도록 선언할 수 있게 해주는 기능.

기존 클래스를 변경하지 않고도 기존 클래스를 확장할 수 있다.

 

 

  1. 확장 함수
fun String.truncate(maxLength: Int): String {
  return if (length <= maxLength) this else substring(0, maxLength)
}

fun main() {
  println("Hello".truncate(3)) // Hel
}
  • receiver의 클래스 이름을 표시하고, 점 이후 함수 이름을 표시한다.
  • 다른 클래스의 멤버와 마찬가지로 함수를 사용할 수 있다.
  • 확장 함수는 클해스 밖에 정의된 함수이므로 클래스의 비공개 멤버에 접근할 수 없다.
  • 클래스 본문 안에서 확장 함수를 정의할 수 있는데, 이 경우엔 비공개 멤버에 접근할 수 있다. (receiver의 멤버인 동시에 확장 함수)

 

fun String.truncate(maxLength: Int): String {
  return if (length <= maxLength) this else substring(0, maxLength)
}

fun main() {
  val trun = "Hello"::truncate
  println(trun(3))
  println(trun(5))
}
 
  • 클래스의 멤버 함수에 대한 bound callable reference처럼 사용할 수 있다.

 

class Foo {
  fun print() {
    println("class member method")
  }
}

fun Foo.print() {
  println("extension method")
}

fun main() {
  Foo(4).print() // class member method
}
 
  • 클래스의 멤버 함수와 확장 함수의 이름이 같다면, 항상 멤버 함수를 우선적으로 선택한다.

 

fun String?.truncate(maxLength: Int): String? {
  if(this == null) return null
  return if (length <= maxLength) this else substring(0, maxLength)
}

fun main() {
  val s: String? = "abc"
  println(s.truncate(3))
}
  • nullable type에 대한 확장 함수도 정의할 수 있다. safe call을 사용하지 않고도 호출할 수 있다.



 

       2. 확장 프로퍼티

val IntRange.leftHalf: IntRange
  get() = start..(start + endInclusive)/2

fun main() {
  println((1..3).leftHalf)
}
 
  • 프로퍼티 이름 앞에 receiver의 타입을 지정하여 확장 프로퍼티를 정의한다.
  • 확장 프로퍼티에는 backing field를 사용할 수 없다. (실제 클래스의 멤버에 들어가는 게 아니기 때문)
    • 그러므로 초기화, field, lateinit을 사용할 수 없다.
    • 항상 게터와 세터를 명시해야 한다.

 

 

 

       3. 동반 확장

class MyClass {
  companion object
}

fun MyClass.Companion.printCompanion() {
  println("companion")
}

fun main() {
  MyClass.printCompanion()
}
  • companion 객체에 대한 확장 함수와 프로퍼티를 정의할 수 있다.
  • 동반 객체의 특성과 동일하게 클래스의 이름만을 사용할 수 있다.
  • 동반 객체가 존재하는 클래스의 경우에만 정의 가능하다.

 

fun aggregate(numbers: IntArray, op: Int.(Int) -> Int): Int {
  var result = numbers.firstOrNull()
    ?: throw IllegalArgumentException("Empty array")
   
  for (i in 1..numbers.lastIndex) result = result.op(numbers[i]) // op(result, numbers[i])
   
  return result
}
  • 람다나 익명 함수에 대해서도 확장 receiver를 사용할 수 있다.
  • 이런 함숫값은 functional type with receiver라는 타입으로 표현된다. 
  • receiver를 가장 첫 번째 파라미터로 넣어서 확장 함수가 아닌 일반 함수 형태로 호출할 수 있다. (런타임시 동일하게 표현되기 때문)

 

fun sum(numbers: IntArray) = aggregate(numbers) { op -> this + op }
  • receiver가 확장인 경우, 람다는 암시적으로 receiver를 가진다. this를 사용해 객체에 접근할 수 있다.

 

fun sum(numbers: IntArray) = aggregate(numbers, fun Int.(op: Int) = this + op)
  • 익명 함수에 대해서도 확장 함수 문법을 사용할 수 있다. receiver 타입을 함수의 파라미터 목록 앞에 추가한다.

 

 

 

       4. receiver가 있는 callable reference

fun aggregate(numbers: IntArray, op: Int.(Int) -> Int): Int {
  var result = numbers.firstOrNull()
    ?: throw IllegalArgumentException("Empty array")
   
  for (i in 1..numbers.lastIndex) result = result.op(numbers[i])
   
  return result
}

fun Int.max(other: Int) = if (this > other) this else other

fun main() {
  val numbers = intArrayOf(1,2,3,4)
  println(aggregate(numbers, Int::plus)) // Int의 멤버 함수
  println(aggregate(numbers, Int::max))  // 확장 함수
}
  • receiver가 있는 함숫값에 대한 callable referene를 만들 수 있다. 
  • bound callable reference와 비슷하지만, receiver 대신 receiver의 타입이 앞에 붙는다는 점이 다르다.
  • receiver가 없는 callable reference를 receiver가 필요한 함숫값으로 전달될 수 있다. (반대도 가능)

 

 

728x90