본문

170828(월) - Kotlin docs (Extensions)

Kotlin docs



Extensions

- Kotlin supports extension functions & extension properties



Extension Functions

- receiver type을 선언해야한다.

- this 키워드는 receiver object


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 l = mutableListOf(1, 2, 3) l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'


- 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 }



Extension are resolved statically

- class에 새 member를 삽입하지 않고 dot-notation으로 call 가능한 새 function을 만든다.

- extension functions는 statically하게 전달된다

→ receiver type에 의해서 virtual하지 않다.


- extension functions는 function이 호출되는 expression type에 의해서 결정

→ runtime에 expression을 평가한 결과의 type에 의해서가 아니다.


open class C class D: C() fun C.foo() = "c" fun D.foo() = "d" fun printFoo(c: C) { println(c.foo()) } printFoo(D())


- "c"가 프린트 된다.

→ 호출되는 extension function은 C class의 parameter c의 type에만 의존


- class에 member function을 가지고 있고 동일한 receiver type을 가진 extension function가 정의되어 있다면,  same name을 사용하고 주어진 parameter에 적용할 수 있는 경우 memeber가 항상 win이다.


class C { fun foo() { println("member") } } fun C.foo() { println("extension") }


ㆍ"member"가 프린트 된다.


- extension function이 이름은 같지만 signature가 다른 member function을 overload하는것은 괜찮다.


class C { fun foo() { println("member") } } fun C.foo(i: Int) { println("extension") }



Nullable Recevier

- extensions는 nullable receiver type으로 정의될 수 있다.

- 이러한 extension은 value가 null인 경우에도 object variable을 call 가능하며 body에서 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() }



Extension Properties

- 실제로 member들을 class에 insert하지 않기 때문에 backing field를 가지는 효율적인 방법이 없다.

→ initalizer가 extension properties를 허용하지 않는 이유

→ getter / setter 사용



val <T> List<T>.lastIndex: Int get() = size - 1


val Foo.bar = 1 // error: initializers are not allowed for extension properties



Companion Object Extensions

- 만약 class가 companion object를 가지고 있다면, companion object의 extension function과 properties를 정의 가능하다.


class MyClass { companion object { } // will be called "Companion" } fun MyClass.Companion.foo() { // ... }


- companion object의 regular member와 마찬가지로 class name 만 qualifier로 호출 가능하다.


MyClass.foo()



Scope of Extensions

- 대부분의 경우 extension을 top level (ex: directly under package)


package foo.bar fun Baz.goo() { ... }


- declaring package 외부에서 사용하려면 call site에서 import가 필요하다.


package com.example.usage import foo.bar.goo // importing all extensions by name "goo" // or import foo.bar.* // importing everything from "foo.bar" fun usage(baz: Baz) { baz.goo() }



Declaring Extensions as Members

- class내에서 다른 class의 extension을 선언 가능

- multiple implicit receivers가 있다.

- 이 receiver는 qualifier 없이 access가능


- extension이 선언된 class의 instance를 dispatch receiver 라고 한다.

- extension method의 receiver type의 instance를 extension receiver 라고 한다.


class D { fun bar() { ... } } class C { fun baz() { ... } fun D.foo() { bar() // calls D.bar baz() // calls C.baz } fun caller(d: D) { d.foo() // call the extension function } }


- dispatch receiver와 extension receiver간의 name conflict의 경우 extension receiver가 상위

- dispatch reciever를 참조하려면 정규화 구문을 사용


class C { fun D.foo() { toString() // calls D.toString() this@C.toString() // calls C.toString() }


- member로 선언된 extension은 open으로 선언 가능하고, sub class에서는 override 될 수 있다.

→ dispatch receiver는 virtual 이지만, extension receiver에 대해서는 static 이라는것을 의미


open class D { } class D1 : D() { } open class C { open fun D.foo() { println("D.foo in C") } open fun D1.foo() { println("D1.foo in C") } fun caller(d: D) { d.foo() // call the extension function } } class C1 : C() { override fun D.foo() { println("D.foo in C1") } override fun D1.foo() { println("D1.foo in C1") } } C().caller(D()) // prints "D.foo in C" C1().caller(D()) // prints "D.foo in C1" - dispatch receiver is resolved virtually C().caller(D1()) // prints "D.foo in C" - extension receiver is resolved statically



Motivation

- Java의 *Utils 나 java.util.Collections 등이 unpleasant 한 점은 다음과 같다.


// Java Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))


- class name은 항상 이런식으로 가져온다. 이를 static import 시키면


// Java swap(list, binarySearch(list, max(otherList)), max(list))


- 조금 더 나아졌지만 IDE의 powerful code completion은 없다.


// Java list.swap(list.binarySearch(otherList.max()), list.max())


- 그러나 List class 안에 모든 메소드를 집어넣기를 원하는것은 아니다. 이럴때 extension이 도움이 될 것이다.

공유

댓글