확장

클래스를 상속하지 않고, 혹은 Decorator 등의 임의의 타입의 디자인 패턴을 사용하지 않고 클래스에 새로운 기능을 확장

확장함수

확장함수를 선언하기 위해서는 함수 이름 앞에 확장될 타입을 추가할 필요가 있다.

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' 는 리스트 객체
    this[index1] = this[index2]
    this[index2] = tmp
}

swap 기능을 추가한 MutableList<Int>

확장함수내의 this 키워드는 리시버 객체 (점 기호 앞에 넘겨진 객체)

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
}

이 함수는 MutableList<T>의 제네릭 형태로도 가능

제네릭 타입 파라매터를 선언하고서 리시버 타입으로 합수명을 사용할수 있다. 제네릭 함수 참고

정적인 확장 함수

확장기능은 실제로 확장하는 클래스를 변경하는 것은 아니다. 단순히 그 타입의 변수의 점 표기에 새로운 함수를 호출할수 있도록 하는 것.

실행할 확장 함수는 정적으로 결정한다. 확장 함수를 호출하는 코드의 타 입으로 호출할 확장 함수를 결정한다. 런타임에 그 식을 평가한 결과 타입으로 결정하지 않는다

open class C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

fun printFoo(c: C) {
    println(c.foo())
}

printFoo(D())

c 가 출력. 호출되는 확장함수 c는 C클래스에 있는 파라매터로 선언된 타입에만 의존하기 때문

클래스에 멤버 함수가 있고, 동일한 반환타입, 동일한 이름을 가지고, 주어진 인수에 적용가능한 확장함수가 정의되어 있는 경우 멤버가 항상 우선시된다.

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

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

Nullable Receiver

확장자는 nullable한 타입으로 정의되어 있다는 것에 주의. 이러한 확장은 그 값이 null 인 경우에도 객체 변수에서 호출 가능하고 본문에서 this == null 으로 체크할 수 있다. 이것은 null을 체크하지 않고 Kotlin에서 toString()을 호출할 수 있는 이유.

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

확장 프로퍼티

함수와 동일하게 Kotlin은 확장 프로퍼티를 지원

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

확장기능은 실제로 멤버를 클래스에 주입하지않으므로, 확장 프로퍼티는 Backing Field를 가지지 않음. 이것이 확장 프로퍼티에 대해 initializer를 허용하지 않는 이유이다. 프로퍼티 동작은 getters/setter를 명시적으로 제공하는 것만 정의 가능하다.

val Foo.bar = 1 // 에러 : 확장 프로퍼티에 대한 initializer는 허용하지 않음

컴피니언 오브젝트 확장

클래스에 컴피니언 오브젝트가 정의되어있는 경우, 컴피니언 오브젝트의 확장기능과 프로퍼티를 정의가능

class MyClass {
    companion object { }  // "Companion" 으로 호출됨
}

fun MyClass.Companion.foo() {
    // ...
}

컴피니언 오브젝트로 일반적인 멤버와 동일하게 클래스명으로 호출가능

MyClass.foo()

확장 범위

대부분 최상위 레벨인 패키지의 아래에 확장을 정의

package foo.bar

fun Baz.goo() { ... }

확장을 선언하고 있는 패키지 밖에서 사용할 경우 호출측에서 import 해야함

package com.example.usage

import foo.bar.goo // "goo"의 모든 확장을 임포트
                   // 혹은
import foo.bar.*   // "foo.bar"에서 모든 것을 임포트

fun usage(baz: Baz) {
    baz.goo()
)

import 참조

멤버로 확장기능 선언

클래스 안에서 다른 클래스의 확장을 선언할 수 있다. 확장 내에서는 복수의 암묵적인 리시버가 잇다. 객체의 멤버에는 제한자없이 접근가능하다. 확장이 선언된 클래스의 인스턴스는 디스패치 리시버라고 하며, 확장 메소드의 리시버 타입의 인스턴스는 확장 리시버라고 한다.

class D {
    fun bar() { ... }
}

class C {
    fun baz() { ... }

    fun D.foo() {
        bar()   // D.bar 호출
        baz()   // C.baz 호출
    }

    fun caller(d: D) {
        d.foo()   // 확장 함수 호출
    }
}

디스패치 리시버와 확장 리시버의 이름이 충돌하는 경우, 확장 리시버가 우선된다. 디스패치 리시버의 멤버를 참조할 경우에는 한정된 this 구문을 사용 가능하다

class C {
    fun D.foo() {
        toString()         // D.toString() 호출
        [email protected]()  // C.toString() 호출
    }

멤버로 선언된 open 확장은 서브 클래스로 선언되고 오버라이드된다. 이는 확장 함수를 디스패치 리시버 타입에 따라 선택한다는 것을 의미한다. 하지만 확장 리시버 타입은 정적이다.

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()   // 확장 함수 호출
    }
}

class C1 : C() {
    override fun D.foo() {
        println("D.foo in C1")
    }

    override fun D1.foo() {
        println("D1.foo in C1")
    }
}

C().caller(D())   // "D.foo in C" 출력
C1().caller(D())  // "D.foo in C1" 출력 - 디스패치 리시버를 virtually하게 선택
C().caller(D1())  // "D.foo in C" 출력 - 확장 리시버를 정적으로 선택

동기

자바에서는 FileUtils, StringUtils 처럼 “*Utils”라는 이름을 갖는 클래스에 익숙하다. 잘 알려진 java.util.Collections 도 이에 속한다. 이런 유틸리티 클래스가 싫은 이유는 코드가 다음과 같은 모습을 띄기 때문이다.

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

정적 임포트를 사용하면 다음과 같이 된다

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

다음 코드처럼 할 수 있다면 훨씬 나을 것이다.

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

하지만 List 클래스에 가능한 메소드를 전부 구현하고 싶지않는다, 그렇죠? 이것이 확장이 우리를 도움이 되는 곳입니다.

results matching ""

    No results matching ""