Kotlin
Uitbreidingsmethoden
Zoeken…
Syntaxis
- leuk TypeName.extensionName (params, ...) {/ * body * /} // Declaration
- leuk <T: Any> TypeNameWithGenerics <T> .extensionName (params, ...) {/ * body * /} // Declaration with Generics
- myObj.extensionName (args, ...) // aanroep
Opmerkingen
Extensies worden statisch opgelost. Dit betekent dat de te gebruiken uitbreidingsmethode wordt bepaald door het referentietype van de variabele waartoe u toegang hebt; het maakt niet uit wat het type van de variabele is tijdens runtime, dezelfde uitbreidingsmethode wordt altijd aangeroepen. Dit komt omdat het declareren van een uitbreidingsmethode geen lid toevoegt aan het type ontvanger .
Extensies op het hoogste niveau
Uitbreidingsmethoden op het hoogste niveau zijn niet opgenomen in een klasse.
fun IntArray.addTo(dest: IntArray) {
for (i in 0 .. size - 1) {
dest[i] += this[i]
}
}
Hierboven is een uitbreidingsmethode gedefinieerd voor het type IntArray
. Merk op dat het object waarvoor de uitbreidingsmethode is gedefinieerd (de ontvanger genoemd ) wordt geopend met het trefwoord this
.
Deze extensie kan zo worden genoemd:
val myArray = intArrayOf(1, 2, 3)
intArrayOf(4, 5, 6).addTo(myArray)
Potentiële valkuil: extensies worden statisch opgelost
De uitbreidingsmethode die moet worden aangeroepen, wordt tijdens het compileren bepaald op basis van het referentietype van de variabele die wordt gebruikt. Het maakt niet uit wat het type van de variabele is tijdens runtime, dezelfde uitbreidingsmethode wordt altijd aangeroepen.
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())
In het bovenstaande voorbeeld wordt "Defined for Super"
afgedrukt, omdat het gedeclareerde type van de variabele myVar
Super
.
Voorbeeld dat zich lang uitstrekt om een voor mensen leesbare string te maken
Gegeven elke waarde van het type Int
of Long
om een voor mensen leesbare string te maken:
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()
}
Dan gemakkelijk gebruikt als:
println(1999549L.humanReadable())
println(someInt.humanReadable())
Voorbeeld van uitbreiding van Java 7+ Path-klasse
Een veelvoorkomende use case voor uitbreidingsmethoden is het verbeteren van een bestaande API. Hier zijn voorbeelden van het toevoegen van exist
, notExists
en deleteRecursively
aan de Java 7+ Path
klasse:
fun Path.exists(): Boolean = Files.exists(this)
fun Path.notExists(): Boolean = !this.exists()
fun Path.deleteRecursively(): Boolean = this.toFile().deleteRecursively()
Die nu in dit voorbeeld kan worden opgeroepen:
val dir = Paths.get(dirName)
if (dir.exists()) dir.deleteRecursively()
Extensiefuncties gebruiken om de leesbaarheid te verbeteren
In Kotlin kun je code schrijven zoals:
val x: Path = Paths.get("dirName").apply {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Maar het gebruik van apply
is niet zo duidelijk over uw intentie. Soms is het duidelijker om een soortgelijke uitbreidingsfunctie te maken om de actie in feite te hernoemen en deze voor de hand liggend te maken. Dit mag niet uit de hand lopen, maar voor veel voorkomende acties zoals verificatie:
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
}
U kunt de code nu schrijven als:
val x: Path = Paths.get("dirName") verifiedWith {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Dat laat mensen nu weten wat ze kunnen verwachten binnen de lambda-parameter.
Merk op dat de parameter type T
voor verifiedBy
hetzelfde is als T: Any?
wat betekent dat zelfs nulbare typen die versie van de extensie kunnen gebruiken. Hoewel verifiedWith
niet-nullable vereist.
Voorbeeld van uitbreiding van Java 8 tijdelijke klassen om een ISO-opgemaakte string te maken
Met deze verklaring:
fun Temporal.toIsoString(): String = DateTimeFormatter.ISO_INSTANT.format(this)
U kunt nu eenvoudig:
val dateAsString = someInstant.toIsoString()
Uitbreidingsfuncties voor begeleidende objecten (weergave van statische functies)
Als u een klasse wilt uitbreiden alsof u een statische functie bent, bijvoorbeeld voor klasse Something
add statical looking function fromString
, kan dit alleen werken als de klasse een bijbehorend object heeft en dat de uitbreidingsfunctie op het bijbehorende object is aangegeven :
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
}
Lazy uitbreiding eigenschap oplossing
Stel dat u een extensie-eigenschap wilt maken die duur is om te berekenen. Daarom wilt u de berekening in de cache opslaan met behulp van de luie afgevaardigde en verwijzen naar de huidige instantie ( this
), maar u kunt het niet doen, zoals uitgelegd in de Kotlin-uitgaven KT-9686 en KT-13053 . Echter, er is een officiële workaround hier verstrekt .
In het voorbeeld is de extensie-eigenschap color
. Het maakt gebruik van een expliciete colorCache
die gebruikt kan worden met this
omdat er geen lazy
is noodzakelijk:
class KColor(val value: Int)
private val colorCache = mutableMapOf<KColor, Color>()
val KColor.color: Color
get() = colorCache.getOrPut(this) { Color(value, true) }
Uitbreidingen voor eenvoudiger referentie Bekijken vanuit code
U kunt extensies gebruiken voor referentie View, geen boilerplate meer nadat u de views hebt gemaakt.
Original Idea is van Anko Library
uitbreidingen
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
Gebruik
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) }