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() // 확장 함수 호출
}
}
파견 수신자와 내선 수신자 간의 이름 충돌이있는 경우 내선 수신자가 우선합니다. 디스패치 수신자의 멤버를 참조하려면
사용할 수 있습니다 .
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 |