본문 바로가기

프로그래밍/kotlin

코틀린 - 확장

반응형

C # 및 Gosu와 유사한 코틀린은 클래스에서 상속하거나 데코레이터와 같은 모든 유형의 디자인 패턴을 사용하지 않고도 새로운 기능으로 클래스를 확장 할 수있는 기능을 제공합니다. 이것은 

확장

 이라는 특별한 선언을 통해 이루어진다 코틀린은 

확장 기능

 과 

확장 속성을

 지원 합니다 .

확장 함수

확장 함수을 선언하려면 

수신자 유형

 , 즉 확장되는 유형 을 접두어로 사용해야합니다 다음은 

swap

함수를 다음에 추가합니다 

MutableList<Int>

.

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this'는 리스트에 해당합니다.
    this[index1] = this[index2]
    this[index2] = tmp
}

확장 함수 의 

this

 키워드는 수신자 객체 (점 앞에 전달 된 객체)에 해당합니다. 이제 우리는 다음과 같은 함수를 호출 할 수 있습니다 

MutableList<Int>

.

val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'swap()'에서 'this'는 '1'의 값을 갖습니다.

물론,이 함수는 어떤 것에 대해서도 이해할 

MutableList<T>

수 있으며 일반적인 것으로 만들 수 있습니다.

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' 는 리스트에 해당합니다.
    this[index1] = this[index2]
    this[index2] = tmp
}

제네릭 형식 매개 변수는 함수 형식 이름이 수신기 형식 식에서 사용 가능하기 전에 선언합니다. 

일반 함수를

 참조하십시오 .

확장 함수는 정적으로 해결됩니다.

확장은 실제로 확장 한 클래스를 수정하지 않습니다. 확장을 정의하면 클래스에 새 멤버를 삽입하지 않고이 유형의 변수에 점 표기법을 사용하여 호출 할 수있는 새 함수를 만들기 만합니다.확장 함수가 

정적으로

 전달된다는 점을 강조하고 싶습니다 . 즉, 수신기 유형별로 가상 함수 가 아닙니다. 즉, 호출되는 확장 함수는 런타임에 해당 표현식을 평가 한 결과의 유형이 아니라 함수가 호출되는 표현식의 유형에 따라 결정됩니다. 예 :

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

 

c.foo()

어떤 

c

형식 이든 부르면 

C

"확장자"가 아닌 "멤버"가 출력됩니다.그러나 확장 함수가 이름은 같지만 서명이 다른 멤버 함수를 오버로드하는 것은 완벽하게 괜찮습니다.

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

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

에 대한 호출 

C().foo(1)

은 "확장자"를 인쇄합니다.

Nullable 리서버

확장자는 nullable 리서버 유형으로 정의 될 수 있습니다. 이러한 확장은 값이 null 인 경우에도 객체 변수에서 호출 할 수 있으며 

this == null

본문 내부에서 확인할 수 있습니다 이것은 널 (null)을 확인하지 않고 코틀린에서 toString ()을 호출 할 수 있도록 허용합니다. 점검은 확장 함수 내에서 수행됩니다.

fun Any?.toString(): String {
    if (this == null) return "null"
    // 검사 이후에 'this'를 non-null 타입으로 자동 변환하므로
    // 다음의 toString()은 Any 클래스의 멤버 함수를 사용합니다.
    return toString()
}

확장 프로퍼티

함수와 마찬가지로 코틀린도 확장 프로퍼티를 지원합니다.

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

확장 기능은 실제로 멤버를 클래스에 삽입하지 않기 때문에 확장 속성에 대한 

배킹 필드

 가있는 효율적인 방법은 없습니다 이것이 

확장 프로퍼티에 대해 initializer를 허용하지 않는

 이유 입니다. 그들의 행동은 getters / setter를 명시 적으로 제공함으로써 정의 할 수 있습니다.예:

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

컴패니언 객체 확장

클래스에 

컴패니언 객체가

 정의 되어있는 경우 컴패니언 객체의 확장 함수 및 속성을 정의 할 수도 있습니다.

class MyClass {
    companion object { }  // "Companion"으로 불립니다.
}

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

컴패니언 객체의 일반 멤버와 마찬가지로 클래스 이름 만 한정자로 호출 할 수 있습니다.

MyClass.foo()

확장의 범위

대부분의 경우 확장을 최상위 레벨, 즉 직접 패키지 아래에 정의합니다.

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

이러한 확장을 선언 패키지 외부에서 사용하려면 호출 사이트에서 해당 확장을 가져와야합니다.

package com.example.usage

import foo.bar.goo // "goo"의 모든 확장을 임포트
                   // 또는
import foo.bar.*   // "foo.bar"로부터 모두 임포트

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

자세한 내용은 

임포트

 을 참조하십시오.

멤버로 확장을 선언하기

클래스 안에서 다른 클래스의 확장을 선언 할 수 있습니다. 이러한 확장에는 여러 개의 

암시 적 수신기

 가 있습니다.이 수신기 는 한정자없이 액세스 할 수 있습니다. 확장이 선언 된 클래스의 인스턴스를 

디스패치 리시버

 라고하며, 확장 메서드의 수신기 유형 인스턴스를 

확장 리시버기

라고 합니다 .

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() 호출
        this@C.toString()  // 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()   // 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

동기

: 자바에서는 

FileUtils

StringUtils처럼 "*Utils"라는 이름을 갖는 클래스에 익숙합니다.

유명한 

java.util.Collections도 이에

 속한다. 그리고 이러한 Utils 클래스가 싫은 이유는 사용하는 코드가 다음과 같이 보인다는 것입니다.

// 자바
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list));

이러한 클래스 이름은 항상 방해 받고 있습니다. 정적 가져 오기를 사용하여 다음을 얻을 수 있습니다.

// 자바
swap(list, binarySearch(list, max(otherList)), max(list));

이것은 조금 나아졌지만 IDE의 강력한 코드 작성으로 인한 도움은 거의 또는 전혀 없습니다. 우리가 말할 수 있다면 그것은 훨씬 더 좋을 것입니다 :

// 자바
list.swap(list.binarySearch(otherList.max()), list.max());

그러나 클래스 내에서 가능한 모든 메소드를 구현하지 않으려 고합니다 

List

확장 프로그램이 도움이되는 곳입니다.

반응형

'프로그래밍 > kotlin' 카테고리의 다른 글

코틀린 - sealed 클래스  (0) 2021.04.26
코틀린 - 데이터 클래스  (0) 2021.04.25
코틀린 - 함수  (0) 2021.04.18
코틀린 - 고차 함수와 람다  (0) 2021.04.18
코틀린 - 인라인 함수  (0) 2021.04.18