Kotlin
Métodos de extensión
Buscar..
Sintaxis
- fun TypeName.extensionName (params, ...) {/ * body * /} // Declaración
- fun <T: Any> TypeNameWithGenerics <T> .extensionName (params, ...) {/ * body * /} // Declaración con genéricos
- myObj.extensionName (args, ...) // invocación
Observaciones
Las extensiones se resuelven estáticamente . Esto significa que el método de extensión que se utilizará está determinado por el tipo de referencia de la variable a la que está accediendo; No importa cuál sea el tipo de la variable en el tiempo de ejecución, siempre se llamará al mismo método de extensión. Esto se debe a que declarar un método de extensión en realidad no agrega un miembro al tipo de receptor .
Extensiones de nivel superior
Los métodos de extensión de nivel superior no están contenidos dentro de una clase.
fun IntArray.addTo(dest: IntArray) {
for (i in 0 .. size - 1) {
dest[i] += this[i]
}
}
Encima se define un método de extensión para el tipo IntArray
. Tenga en cuenta que se accede al objeto para el que se define el método de extensión (llamado receptor ) con la palabra clave this
.
Esta extensión se puede llamar así:
val myArray = intArrayOf(1, 2, 3)
intArrayOf(4, 5, 6).addTo(myArray)
Posible trampa: las extensiones se resuelven de forma estática
El método de extensión a llamar se determina en tiempo de compilación en función del tipo de referencia de la variable a la que se accede. No importa cuál sea el tipo de la variable en el tiempo de ejecución, siempre se llamará al mismo método de extensión.
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())
El ejemplo anterior imprimirá "Defined for Super"
, porque el tipo declarado de la variable myVar
es Super
.
Muestra que se extiende por mucho tiempo para representar una cadena humana legible
Dado cualquier valor de tipo Int
o Long
para representar una cadena legible por humanos:
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()
}
Luego se usa fácilmente como:
println(1999549L.humanReadable())
println(someInt.humanReadable())
Ejemplo de extensión de Java 7+ clase de ruta
Un caso de uso común para los métodos de extensión es mejorar una API existente. Aquí hay ejemplos de notExists
agregar exist
, notExists
y deleteRecursively
a la clase Java 7+ Path
:
fun Path.exists(): Boolean = Files.exists(this)
fun Path.notExists(): Boolean = !this.exists()
fun Path.deleteRecursively(): Boolean = this.toFile().deleteRecursively()
Que ahora se puede invocar en este ejemplo:
val dir = Paths.get(dirName)
if (dir.exists()) dir.deleteRecursively()
Usando funciones de extensión para mejorar la legibilidad
En Kotlin puedes escribir código como:
val x: Path = Paths.get("dirName").apply {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Pero el uso de apply
no es tan claro en cuanto a su intención. A veces es más claro crear una función de extensión similar para, en efecto, cambiar el nombre de la acción y hacerla más evidente. No se debe permitir que esto se salga de control, pero para acciones muy comunes como la verificación:
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
}
Ahora puedes escribir el código como:
val x: Path = Paths.get("dirName") verifiedWith {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Que ahora la gente sepa qué esperar dentro del parámetro lambda.
Tenga en cuenta que el parámetro de tipo T
para verifiedBy
es el mismo que T: Any?
lo que significa que incluso los tipos anulables podrán usar esa versión de la extensión. Aunque verifiedWith
requiere no anulable.
Ejemplo de extensión de clases temporales de Java 8 para representar una cadena con formato ISO
Con esta declaración:
fun Temporal.toIsoString(): String = DateTimeFormatter.ISO_INSTANT.format(this)
Ahora puedes simplemente:
val dateAsString = someInstant.toIsoString()
Funciones de extensión a objetos complementarios (apariencia de funciones estáticas)
Si desea extender una clase como-si es una función estática, por ejemplo, para la clase Something
agregue la función de aspecto estático desde la fromString
, esto solo puede funcionar si la clase tiene un objeto complementario y la función de extensión se ha declarado en el objeto complementario. :
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
}
Solución perezosa de la propiedad de la extensión
Supongamos que desea crear una propiedad de extensión que sea costosa de computar. Así que le gustaría almacenar en caché el cálculo, utilizando el delegado propiedad perezosa y se refieren a instancia actual ( this
), pero no puede hacerlo, como se explica en el Kotlin emite KT-9686 y KT-13053 . Sin embargo, hay una solución oficial proporcionada aquí .
En el ejemplo, la propiedad de extensión es color
. Utiliza un colorCache
explícito que puede usarse con this
ya que no es necesario lazy
:
class KColor(val value: Int)
private val colorCache = mutableMapOf<KColor, Color>()
val KColor.color: Color
get() = colorCache.getOrPut(this) { Color(value, true) }
Extensiones para una referencia más fácil Vista desde el código
Puede usar extensiones para la vista de referencia, no más repeticiones después de crear las vistas.
La idea original es de la biblioteca de Anko
Extensiones
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
Uso
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) }