Kotlin
Erweiterungsmethoden
Suche…
Syntax
- fun TypeName.extensionName (Parameter, ...) {/ * body * /} // Deklaration
- fun <T: Any> TypeNameWithGenerics <T> .extensionName (params, ...) {/ * body * /} // Deklaration mit Generics
- myObj.extensionName (args, ...) // Aufruf
Bemerkungen
Erweiterungen werden statisch aufgelöst. Dies bedeutet, dass die zu verwendende Erweiterungsmethode durch den Referenztyp der Variablen bestimmt wird, auf die Sie zugreifen. Es spielt keine Rolle, welchen Typ die Variable zur Laufzeit hat, es wird immer dieselbe Erweiterungsmethode aufgerufen. Dies liegt daran, dass beim Deklarieren einer Erweiterungsmethode dem Empfängertyp kein Mitglied tatsächlich hinzugefügt wird .
Erweiterungen der obersten Ebene
Erweiterungsmethoden der obersten Ebene sind nicht in einer Klasse enthalten.
fun IntArray.addTo(dest: IntArray) {
for (i in 0 .. size - 1) {
dest[i] += this[i]
}
}
Oben ist eine Erweiterungsmethode für den Typ IntArray
. Beachten Sie, dass auf das Objekt, für das die Erweiterungsmethode definiert ist (als Empfänger bezeichnet ), mit dem Schlüsselwort this
zugegriffen wird.
Diese Erweiterung kann wie folgt aufgerufen werden:
val myArray = intArrayOf(1, 2, 3)
intArrayOf(4, 5, 6).addTo(myArray)
Mögliche Gefahr: Erweiterungen werden statisch aufgelöst
Die aufzurufende Erweiterungsmethode wird zur Kompilierzeit basierend auf dem Referenztyp der Variablen bestimmt, auf die zugegriffen wird. Es spielt keine Rolle, was der Variablentyp zur Laufzeit ist, es wird immer dieselbe Erweiterungsmethode aufgerufen.
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())
Im obigen Beispiel wird "Defined for Super"
gedruckt, da der deklarierte Typ der Variablen myVar
Super
.
Beispiel, das sich lang ausdehnt, um eine für Menschen lesbare Zeichenfolge darzustellen
Geben Sie einen beliebigen Wert vom Typ Int
oder Long
, um eine für Menschen lesbare Zeichenfolge darzustellen:
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()
}
Dann einfach als:
println(1999549L.humanReadable())
println(someInt.humanReadable())
Beispiel für die Erweiterung der Klasse Java 7+ Path
Ein üblicher Anwendungsfall für Erweiterungsmethoden ist die Verbesserung einer vorhandenen API. Hier einige Beispiele für das Hinzufügen von exist
, notExists
und deleteRecursively
zur Java 7+ Path
Klasse:
fun Path.exists(): Boolean = Files.exists(this)
fun Path.notExists(): Boolean = !this.exists()
fun Path.deleteRecursively(): Boolean = this.toFile().deleteRecursively()
Was kann nun in diesem Beispiel aufgerufen werden:
val dir = Paths.get(dirName)
if (dir.exists()) dir.deleteRecursively()
Erweiterungsfunktionen verwenden, um die Lesbarkeit zu verbessern
In Kotlin könnte man Code schreiben wie:
val x: Path = Paths.get("dirName").apply {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Die Verwendung von apply
ist jedoch nicht so klar wie Ihre Absicht. Manchmal ist es klarer, eine ähnliche Erweiterungsfunktion zu erstellen, um die Aktion tatsächlich umzubenennen und sie selbstverständlich zu machen. Dies sollte nicht außer Kontrolle geraten, sondern bei sehr häufigen Aktionen wie der Verifizierung:
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
}
Sie könnten jetzt den Code schreiben als:
val x: Path = Paths.get("dirName") verifiedWith {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Was nun die Leute wissen lässt, was innerhalb des Lambda-Parameters zu erwarten ist.
Beachten Sie, dass der Typparameter T
für verifiedBy
mit T: Any?
Das bedeutet, dass auch nullfähige Typen diese Version der Erweiterung verwenden können. Obwohl verifiedWith
nicht nullfähig ist.
Beispiel zur Erweiterung von Java 8 Temporal-Klassen zum Rendern einer ISO-formatierten Zeichenfolge
Mit dieser Erklärung:
fun Temporal.toIsoString(): String = DateTimeFormatter.ISO_INSTANT.format(this)
Sie können jetzt einfach:
val dateAsString = someInstant.toIsoString()
Erweiterungsfunktionen für Begleitobjekte (Darstellung statischer Funktionen)
Wenn Sie eine Klasse als statische Funktion erweitern möchten, z. B. für die Klasse Something
add statisch fromString
Funktion, kann dies nur funktionieren, wenn die Klasse über ein Begleitobjekt verfügt und die Erweiterungsfunktion für das Begleitobjekt deklariert wurde :
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
}
Problemumgehung für faul Erweiterungs-Eigenschaften
Angenommen, Sie möchten eine Erweiterungseigenschaft erstellen, deren Berechnung teuer ist. Daher möchten Sie die Berechnung zwischenspeichern, indem Sie den verzögerten Eigenschaftsdelegierten verwenden und auf die aktuelle Instanz ( this
) verweisen. this
ist jedoch nicht möglich, wie in den Kotlin-Ausgaben KT-9686 und KT-13053 erläutert. Es wird jedoch eine offizielle Problemumgehung bereitgestellt .
Im Beispiel ist die Erweiterungseigenschaft color
. Es verwendet eine explizite colorCache
, die mit verwendet werden kann this
als nicht lazy
ist notwendig:
class KColor(val value: Int)
private val colorCache = mutableMapOf<KColor, Color>()
val KColor.color: Color
get() = colorCache.getOrPut(this) { Color(value, true) }
Erweiterungen zum leichteren Nachschlagen View from code
Sie können Erweiterungen für die Referenzansicht verwenden, keine Boilerplate mehr, nachdem Sie die Ansichten erstellt haben.
Ursprüngliche Idee stammt von Anko Library
Erweiterungen
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
Verwendungszweck
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) }