Search
moon
sun

Extensions

•
Kotlin은 클래스에서 상속하거나 Decorator 와 같은 디자인 패턴을 사용하지 않고도 클래스나 인터페이스를 새로운 기능으로 확장할 수 있는 기능을 제공한다.
•
이는 확장이라고 하는 특수 선언을 통해 수행된다.
•
수정할 수 없는 타사 라이브러리의 클래스나 인터페이스에 대한 새 함수를 작성할 수 있다.
•
이 메커니즘을 확장 함수라고 하고 기존 클래스에 대한 새 속성을 정의할 수 있는 확장 속성도 존재한다.

확장 함수

•
확장 함수를 선언하려면 이름 앞에 확장되는 타입을 나타내는 수신자 타입을 접두사로 붙인다.
•
확장 함수 내부의 키워드 this는 수신자 객체에 해당한다.
fun MutableList<Int>.swap(index1: Int, index2: Int) { val tmp = this[index1] // 'this' corresponds to the list this[index1] = this[index2] this[index2] = tmp }
Kotlin
복사
•
제네릭 타입 매개변수를 통해 일반화할 수 있다.
fun <T> MutableList<T>.swap(index1: Int, index2: Int) { val tmp = this[index1] // 'this' corresponds to the list this[index1] = this[index2] this[index2] = tmp }
Kotlin
복사

확장 함수는 정적으로 결정된다.

•
확장은 실제로 확장하는 클래스를 수정하지 않는다.
•
확장을 정의하면 클래스에 새 멤버를 삽입하지 않고 이 타입의 변수에 점 표기법으로 호출 가능한 새 함수를 만든다.
•
확장 함수는 정적으로 전송되므로 어떤 확장 함수가 호출되는지는 수신기 타입에 따라 컴파일 시간에 알려진다.
open class Shape class Rectangle: Shape() fun Shape.getName() = "Shape" fun Rectangle.getName() = "Rectangle" fun printClassName(s: Shape) { println(s.getName()) } printClassName(Rectangle())
Kotlin
복사
•
printClassName의 파라미터 f가 Shape기 때문에 Shape의 확장 함수인 getName을 호출한다.

확장 함수의 우선순위

•
클래스에 멤버 함수와 동일한 수신기 타입, 동일한 이름, 동일한 인수를 가지는 확장 함수가 정의된 경우 항상 멤버 함수가 호출된다.
class Example { fun printFunctionType() { println("Class method") } } fun Example.printFunctionType() { println("Extension function") } Example().printFunctionType() // Class method
Kotlin
복사

오버로딩

•
확장 함수는 동일한 이름이지만 다른 시그니처를 가진 경우 멤버 함수를 오버로드하는 것이 가능하다.
class Example { fun printFunctionType() { println("Class method") } } fun Example.printFunctionType(i: Int) { println("Extension function #$i") } Example().printFunctionType(1) // Extension function #1
Kotlin
복사

Nullable 수신기

•
확장은 nullable 수신기 타입으로 정의할 수 있고 값이 null이더라도 객체 변수에서 호출할 수 있다.
•
수신기가 null이면 this도 null이다.
•
nullable 수신기 타입으로 확장할 때는 컴파일러 오류를 방지하기 위해 함수 본문 내부에서 this == null 검사를 수행하는 것이 좋다
fun Any?.toString(): String { if (this == null) return "null" // After the null check, 'this' is autocast to a non-nullable type, so the toString() below // resolves to the member function of the Any class return toString() }
Kotlin
복사

확장 속성

•
확장 속성은 클래스에 실제 멤버를 추가하는 것이 아니기 때문에 백킹 필드를 가질 수 없다.
•
확장 속성은 초기화자가 허용되지 않으므로 초기값을 지정할 수 없다.
•
확장 속성은 반드시 getter와 setter를 명시적으로 제공해야 한다.
val <T> List<T>.lastIndex: Int get() = size - 1
Kotlin
복사

동반 객체 확장

•
클래스에 companion object가 정의되어 있는 경우 동반 객체에 대해 확장 함수와 확장 속성을 정의할 수 있다.
•
동반 객체의 기본 이름은 Companion으로 동반 객체 확장은 클래스 이름을 사용하여 호출할 수 있다.
class MyClass { companion object { } // will be called "Companion" } // 동반 객체 확장 함수 fun MyClass.Companion.printCompanion() { println("companion") } // 동반 객체 확장 속성 val MyClass.Companion.description: String get() = "This is a companion object." fun main() { MyClass.printCompanion() // 출력: companion println(MyClass.description) // 출력: This is a companion object. }
Kotlin
복사

확장 범위

•
확장 함수와 확장 속성은 일반적으로 확장이 정의된 패키지 내에서만 유효하다는 것을 의미하기 위해 패키지의 최상위 레벨에 정의한다.
package org.example.declarations fun List<String>.getLongestString(): String { return this.maxByOrNull { it.length } ?: "" }
Kotlin
복사
•
이 확장 함수는 org.example.declarations 패키지 내에서만 사용 가능하다.
package org.example.usage import org.example.declarations.getLongestString fun main() { val list = listOf("red", "green", "blue") val longest = list.getLongestString() println(longest) // 출력: green }
Kotlin
복사
•
다른 패키지에서 사용하려면 해당 확장 함수를 import 해야 한다.

확장을 멤버로 선언

•
클래스 내에 다른 클래스에 대한 확장 함수를 선언할 수 있다.
•
이러한 확장 함수 안엔 별도의 수식어 없이 접근할 수 있는 여러 암묵적 수신기가 존재할 수 있다.
•
이러한 확장 함수가 선언된 클래스의 인스턴스를 디스패치 수신기라고 한다.
•
확장 함수의 수신기 타입의 인스턴스를 확장 수신기라고 한다.
class Host(val hostname: String) { fun printHostname() { print(hostname) } } class Connection(val host: Host, val port: Int) { fun printPort() { print(port) } fun Host.printConnectionString() { printHostname() // Host.printHostname() 호출 print(":") printPort() // Connection.printPort() 호출 } fun connect() { /*...*/ host.printConnectionString() // 확장 함수 호출 } } fun main() { Connection(Host("kotl.in"), 443).connect() // Host("kotl.in").printConnectionString() // 오류, 확장 함수는 Connection 클래스 밖에서 사용 불가 }
Kotlin
복사
•
Connection 클래스에서 Host 클래스에 대한 확장 함수 printConnectionString() 을 정의한다.
•
Host.printConnectionString() 의 암묵적 수신기는 Host와 Connection이다.
•
Connection은 디스패치 수신기이다.
•
Host는 확장 수신기이다.

이름 충돌

•
디스패치 수신기와 확장 수신기의 멤버 이름이 충돌하는 경우 확장 수신기의 멤버가 우선된다.
•
디스패치 수신기의 멤버를 참조하려면 this 키워드를 사용해야 한다.
class Connection { fun Host.getConnectionString() { toString() // Host.toString() 호출 this@Connection.toString() // Connection.toString() 호출 } }
Kotlin
복사

확장 함수의 오버라이드

•
멤버로 선언된 확장 함수는 open 으로 선언되어 자식 클래스에서 오버라이드 될 수 있다.
•
디스패치 수신기 타입에 대해 가상으로 디스패치되지만 확장 수신기 타입에 대해서는 정적으로 디스패치된다는 것을 의미한다.
open class Base { } class Derived : Base() { } open class BaseCaller { open fun Base.printFunctionInfo() { println("1") } open fun Derived.printFunctionInfo() { println("2") } fun call(b: Base) { b.printFunctionInfo() // 확장 함수 호출 } } class DerivedCaller: BaseCaller() { override fun Base.printFunctionInfo() { println("3") } override fun Derived.printFunctionInfo() { println("4") } } fun main() { BaseCaller().call(Base()) // "1" BaseCaller().call(Derived()) // "1" DerivedCaller().call(Base()) // "3" DerivedCaller().call(Derived()) // "3" }
Kotlin
복사
•
확장 함수는 기본적으로 정적 디스패치이기 때문에 컴파일 시점에 타입에 따라 어떤 함수가 호출될지 결정된다.
◦
call()의 인자로 Base를 받고 있기 때문에 Base의 자식 클래스인 Derived를 넘겨도 Base의 확장 함수가 호출된다.
•
클래스 멤버로 확장 함수를 선언하면 가상 디스패치가 가능하기 때문에 디스패치 수신기 타입에 따라 어떤 함수가 호출될지 결정된다.
◦
BaseCaller, DerivedCaller 중 어떤 디스패치 수신기냐에 따라서 호출되는 확장 함수가 달라진다.

확장의 가시성

•
확장은 동일한 범위에서 선언된 일반 함수와 동일한 가시성 수정자를 사용한다.
◦
파일의 최상위에 선언된 확장은 동일 파일 내의 다른 private 최상위 선언에 접근할 수 있다.
// File: Example.kt private fun topLevelFunction() { println("I am a private top-level function") } fun String.printWithTopLevelFunction() { topLevelFunction() // 접근 가능 println(this) } fun main() { "Hello".printWithTopLevelFunction() // 출력: I am a private top-level function // Hello }
Kotlin
복사
◦
수신기 타입 밖에 선언된 확장이라면 수신기의 private 또는 protected 멤버들에 접근할 수 없다.
class ExampleClass { private val secret = "This is a secret" fun printSecret() { println(secret) // 접근 가능 } } fun ExampleClass.showSecret() { // println(secret) // 접근 불가 - 컴파일 오류 발생 println("Cannot access private member") } fun main() { val example = ExampleClass() example.printSecret() // 출력: This is a secret example.showSecret() // 출력: Cannot access private member }
Kotlin
복사