kotlin
[kotlin/코틀린] 확장
hrniin
2023. 7. 19. 23:11
728x90
확장
클래스 밖에서 함수나 프로퍼티를 멤버인 것처럼 쓸 수 있도록 선언할 수 있게 해주는 기능.
기존 클래스를 변경하지 않고도 기존 클래스를 확장할 수 있다.
- 확장 함수
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