일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- package
- set
- crawler
- Yocto
- Collection
- Android
- 파이썬
- 웹크롤러
- python
- Flutter
- textstyle
- 콜렉션
- animation
- import
- ML
- 코틀린
- text
- 함수
- kotlin
- List
- 플러터
- pushnamed
- function
- map
- 크롤러
- 클래스
- variable
- 다트
- Class
- DART
- Today
- Total
조용한 담장
코틀린(Kotlin) 클래스(Class) : 확장 (Extensions) 본문
코틀린의 클래스의 확장(Extentsion) 에 대해 살펴보자.
원문 https://kotlinlang.org/docs/reference/extensions.html 을 보며 정리.
kotlin 의 클래스는 상속이나 디자인 패턴인 Decorator 를 통한 기능 확장 외의 방법인 extention 을 제공한다.
예를들어 수정이 불가능한 third-party 라이브러리의 클래스에 함수를 추가할 수 있다.
마치 원래 클래스에 있는 메소드처럼 함수를 추가해 호출할 수 있게 해주는걸 extension functions 라 한다.
property 도 추가할 수 있으며 이를 extension properties 라 한다.
Extension functions
receiver(확장할 대상) type 과 함수의 이름을 이용해 선언한다.
swap 함수를 MutableList 에 추가하는 예제:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'list'
MutableList 의 원래 멤버 함수처럼 호출하여 사용이 가능해 진다.
함수 안의 this 는 확장 함수의 대상이 되는 오브젝트(예제에선 MutableList) 를 가리킨다.
generic 도 가능하다.
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
}
Extensions are resolved statically
extension 은 실제로 클래스를 자체를 수정하는 거나 함수를 추가하는 것이 아니라, 해당 타입의 변수에 . 표기로 호출되는 함수를 새로 만드는 것이다.
확장 함수는 실행중 표현식에 따라 다른 타입을 기반으로 동작하는 것이 아니라 함수 선언시 결정된 타입으로만 호출된다.
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
printClassName 의 파리미터가 s: Shape 로 선언되어 있기 때문에 확장함수는 선언된 타입에 따라 Shape.getName() 가 호출되어 "Shape" 가 출력된다.
클래스의 멤버 함수와 같은 이름, 같은 파라미터를 가진 확장함수를 선언하면 항상 원래 클래스의 멤버 함수가 호출된다.
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
// output:
// Class method
확장 함수가 클래스의 멤버함수를 overload 하는 것은 가능하다.
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType(i: Int) { println("Extension function") }
Example().printFunctionType(1)
Nullable receiver
extension 은 nullable receiver type 으로 정의될 수 있다.
extension 호출되는 대상 object 의 값이 null 일 수 있고 함수 에서 this == null 처럼 값 체크가 가능하다.
fun Any?.toString(): String {
if (this == null) return "null"
// after the null check, 'this' is autocast to a non-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}
위의 예제처럼 구현하면 toString() 을 null 확인 없이 호출할 수 있게 해준다.
Extension properties
확장 함수처럼 확장 property 를 지원한다.
val <T> List<T>.lastIndex: Int
get() = size - 1
확장 property 는 확장 함수와 마찬가지로 대상 오브젝트에 추가 하는것이 아니기 때문에 backingfield 를 가질수 없다.
따라서 initializer 를 쓸 수 없게 되므로 setter, getter 를 사용해야 한다.
val House.number = 1 // error: initializers are not allowed for extension properties
Companion object extensions
companion object 를 가진 클래스도 확장 함수와 프로퍼티를 가질 수 있다.
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() {
MyClass.printCompanion()
}
Scope of extensions
대부분은 package 선언 바로 아래의 최상위 위치에 extensions 를 선언한다.
package org.example.declarations
fun List<String>.getLongestString() { /*...*/}
외부 패키지에서 사용하려면 import 를 사용한다.
package org.example.usage
import org.example.declarations.getLongestString
fun main() {
val list = listOf("red", "green", "blue")
list.getLongestString()
}
Declaring extensions as members
클래스 안에서 다른 클래스를 위한 extensions 를 클래스의 멤버로써 선언할 수 있다.
extension 안에는 한정자(qualifier) 없이 접근될수 있는 오브젝트 멤버들(implicit receivers) 이 여러개가 존재하게 된다.
extension 이 선언된 클래스의 인스턴스를 dispatch receiver 라 하며 extension method 의 receiver type 의 인스턴스는 extension receiver 라 한다.
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() // calls Host.printHostname()
print(":")
printPort() // calls Connection.printPort()
}
fun connect() {
/*...*/
host.printConnectionString() // calls the extension function
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect()
//Host("kotl.in").printConnectionString(443) // error, the extension function is unavailable outside Connection
}
확장 함수 Host.printConnectionString() 안에서는 Host, Connection 모두 접근이 가능하다.
Connection 은 dispatch receiver 가 되고 Host 는 extension receiver 가 된다.
dispatch receiver 와 extension receiver 의 멤버 이름이 이 같은 경우에는 extension receiver 가 우선이 된다.
우선순위와 다르게 dispatch receiver 를 참조하려면 qualified this syntax 를 사용한다.
class Connection {
fun Host.getConnectionString() {
toString() // calls Host.toString() of extension receiver
this@Connection.toString() // calls Connection.toString() of dispatch receiver
}
}
멤버로써 선언된 extension 은 subclass 에서 open 이면서 override 된 멤버가 될 수 있다.
이는 dispatch receiver type 에 따라 가변적(virtual) 이기도 하지만, extension receiver type 에 따라 고정적(static) 인 특징을 가진다.
open class Base { }
class Derived : Base() { }
open class BaseCaller {
open fun Base.printFunctionInfo() {
println("Base extension function in BaseCaller")
}
open fun Derived.printFunctionInfo() {
println("Derived extension function in BaseCaller")
}
fun call(b: Base) {
b.printFunctionInfo() // call the extension function
}
}
class DerivedCaller: BaseCaller() {
override fun Base.printFunctionInfo() {
println("Base extension function in DerivedCaller")
}
override fun Derived.printFunctionInfo() {
println("Derived extension function in DerivedCaller")
}
}
fun main() {
BaseCaller().call(Base()) // "Base extension function in BaseCaller"
DerivedCaller().call(Base()) // "Base extension function in DerivedCaller" - dispatch receiver is resolved virtually
DerivedCaller().call(Derived()) // "Base extension function in DerivedCaller" - extension receiver is resolved statically
}
아래의 예처럼 Extensions 도 visibility 을 가진다.
파일의 최상위에 선언된 extension 은 같은 파일 내에 최상위의 priviate 선언들에도 접근이 가능하다.
receiver type 외부에 extension 이 선언되면 receiver 의 priviate 멤버를 접근할 수 없다.
'kotlin' 카테고리의 다른 글
코틀린(Kotlin) 클래스(Class) : 열거 클래스 (Enum classes) (0) | 2019.12.31 |
---|---|
코틀린(Kotlin) 클래스(Class) : 데이터 클래스 (Data Classes) (0) | 2019.12.31 |
코틀린(Kotlin) 클래스(Class) : Visibility Modifiers (0) | 2019.12.31 |
코틀린(kotlin) 클래스(Class) : 인터페이스 (Interfaces) (0) | 2019.12.31 |
코틀린(Kotlin) 클래스(Class) : 프로퍼티 와 필드 (Properties and Fields) (0) | 2019.12.31 |