Kotlin
Metody rozszerzenia
Szukaj…
Składnia
- fun TypeName.extensionName (parametry, ...) {/ * body * /} // Deklaracja
- fun <T: Any> TypeNameWithGenerics <T> .extensionName (parametry, ...) {/ * body * /} // Deklaracja z danymi rodzajowymi
- myObj.extensionName (argumenty, ...) // wywołanie
Uwagi
Rozszerzenia są rozwiązywane statycznie . Oznacza to, że zastosowana metoda rozszerzenia zależy od typu odniesienia zmiennej, do której uzyskujesz dostęp; nie ma znaczenia, jaki typ zmiennej jest w czasie wykonywania, zawsze będzie wywoływana ta sama metoda rozszerzenia. Wynika to z faktu, że zadeklarowanie metody rozszerzenia tak naprawdę nie dodaje członka do typu odbiornika .
Rozszerzenia najwyższego poziomu
Metody rozszerzenia najwyższego poziomu nie są zawarte w klasie.
fun IntArray.addTo(dest: IntArray) {
for (i in 0 .. size - 1) {
dest[i] += this[i]
}
}
Powyżej metody rozszerzenia zdefiniowano dla typu IntArray
. Zauważ, że do obiektu, dla którego zdefiniowano metodę rozszerzenia (zwanego odbiornikiem ), można uzyskać dostęp za pomocą słowa kluczowego this
.
To rozszerzenie można nazwać tak:
val myArray = intArrayOf(1, 2, 3)
intArrayOf(4, 5, 6).addTo(myArray)
Potencjalna pułapka: rozszerzenia są rozwiązywane statycznie
Metoda rozszerzenia, która ma zostać wywołana, jest określana w czasie kompilacji na podstawie typu referencyjnego zmiennej, do której uzyskiwany jest dostęp. Nie ma znaczenia, jaki typ zmiennej jest w czasie wykonywania, zawsze będzie wywoływana ta sama metoda rozszerzenia.
open class Super
class Sub : Super()
fun Super.myExtension() = "Defined for Super"
fun Sub.myExtension() = "Defined for Sub"
fun callMyExtension(myVar: Super) {
println(myVar.myExtension())
}
callMyExtension(Sub())
Powyższy przykład wypisze "Defined for Super"
, ponieważ zadeklarowany typ zmiennej myVar
to Super
.
Próbka rozciągająca się długo, aby renderować ciąg czytelny dla człowieka
Biorąc pod uwagę dowolną wartość typu Int
lub Long
aby wyświetlić ciąg czytelny dla człowieka:
fun Long.humanReadable(): String {
if (this <= 0) return "0"
val units = arrayOf("B", "KB", "MB", "GB", "TB", "EB")
val digitGroups = (Math.log10(this.toDouble())/Math.log10(1024.0)).toInt();
return DecimalFormat("#,##0.#").format(this/Math.pow(1024.0, digitGroups.toDouble())) + " " + units[digitGroups];
}
fun Int.humanReadable(): String {
return this.toLong().humanReadable()
}
Następnie łatwo używać jako:
println(1999549L.humanReadable())
println(someInt.humanReadable())
Przykład rozszerzenia klasy Java 7+ Path
Typowym przypadkiem użycia metod rozszerzeń jest poprawa istniejącego interfejsu API. Oto przykłady dodawania exist
, notExists
i deleteRecursively
do klasy Java 7+ Path
:
fun Path.exists(): Boolean = Files.exists(this)
fun Path.notExists(): Boolean = !this.exists()
fun Path.deleteRecursively(): Boolean = this.toFile().deleteRecursively()
Które można teraz wywołać w tym przykładzie:
val dir = Paths.get(dirName)
if (dir.exists()) dir.deleteRecursively()
Korzystanie z funkcji rozszerzeń w celu poprawy czytelności
W Kotlin możesz napisać kod taki jak:
val x: Path = Paths.get("dirName").apply {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Ale zastosowanie „ apply
nie jest tak jasne, jak w przypadku twoich intencji. Czasami łatwiej jest stworzyć podobną funkcję rozszerzenia, aby w efekcie zmienić nazwę akcji i uczynić ją bardziej oczywistą. Nie powinno to pozwolić wymknąć się spod kontroli, ale w przypadku bardzo częstych działań, takich jak weryfikacja:
infix inline fun <T> T.verifiedBy(verifyWith: (T) -> Unit): T {
verifyWith(this)
return this
}
infix inline fun <T: Any> T.verifiedWith(verifyWith: T.() -> Unit): T {
this.verifyWith()
return this
}
Możesz teraz napisać kod jako:
val x: Path = Paths.get("dirName") verifiedWith {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Który teraz pozwala ludziom wiedzieć, czego się spodziewać w ramach parametru lambda.
Zauważ, że parametr typu T
dla verifiedBy
jest taki sam jak T: Any?
co oznacza, że nawet typy zerowalne będą mogły korzystać z tej wersji rozszerzenia. Chociaż verifiedWith
wymaga wartości zerowej.
Przykład rozszerzenia klas Java 8 Temporal w celu renderowania łańcucha w formacie ISO
Dzięki tej deklaracji:
fun Temporal.toIsoString(): String = DateTimeFormatter.ISO_INSTANT.format(this)
Możesz teraz po prostu:
val dateAsString = someInstant.toIsoString()
Funkcje rozszerzające do obiektów towarzyszących (wygląd funkcji statycznych)
Jeśli chcesz rozszerzyć klasę tak, jakbyś był funkcją statyczną, na przykład dla klasy Something
dodaj funkcję statycznie wyglądającą fromString
, może to działać tylko wtedy, gdy klasa ma obiekt towarzyszący, a funkcja rozszerzenia została zadeklarowana na obiekcie towarzyszącym :
class Something {
companion object {}
}
class SomethingElse {
}
fun Something.Companion.fromString(s: String): Something = ...
fun SomethingElse.fromString(s: String): SomethingElse = ...
fun main(args: Array<String>) {
Something.fromString("") //valid as extension function declared upon the
//companion object
SomethingElse().fromString("") //valid, function invoked on instance not
//statically
SomethingElse.fromString("") //invalid
}
Obejście właściwości leniwego rozszerzenia
Załóżmy, że chcesz utworzyć właściwość rozszerzenia, której obliczenie jest kosztowne. Dlatego chcesz buforować obliczenia, używając leniwego delegata właściwości i odwołać się do bieżącej instancji ( this
), ale nie możesz tego zrobić, jak wyjaśniono w numerach Kotlin KT-9686 i KT-13053 . Jednak nie jest oficjalnym obejście umieszczono tutaj .
W tym przykładzie właściwością rozszerzenia jest color
. Używa jawnego colorCache
którego można używać z this
ponieważ nie jest konieczne lazy
:
class KColor(val value: Int)
private val colorCache = mutableMapOf<KColor, Color>()
val KColor.color: Color
get() = colorCache.getOrPut(this) { Color(value, true) }
Rozszerzenia dla łatwiejszego przeglądania Widok z kodu
Możesz użyć rozszerzeń dla odniesienia Zobacz, nie więcej płyty kotłowej po utworzeniu widoków.
Oryginalny pomysł autorstwa Anko Library
Rozszerzenia
inline fun <reified T : View> View.find(id: Int): T = findViewById(id) as T
inline fun <reified T : View> Activity.find(id: Int): T = findViewById(id) as T
inline fun <reified T : View> Fragment.find(id: Int): T = view?.findViewById(id) as T
inline fun <reified T : View> RecyclerView.ViewHolder.find(id: Int): T = itemView?.findViewById(id) as T
inline fun <reified T : View> View.findOptional(id: Int): T? = findViewById(id) as? T
inline fun <reified T : View> Activity.findOptional(id: Int): T? = findViewById(id) as? T
inline fun <reified T : View> Fragment.findOptional(id: Int): T? = view?.findViewById(id) as? T
inline fun <reified T : View> RecyclerView.ViewHolder.findOptional(id: Int): T? = itemView?.findViewById(id) as? T
Stosowanie
val yourButton by lazy { find<Button>(R.id.yourButtonId) }
val yourText by lazy { find<TextView>(R.id.yourTextId) }
val yourEdittextOptional by lazy { findOptional<EditText>(R.id.yourOptionEdittextId) }