findview - Campos de extensión en Kotlin
kotlin extension (4)
Es fácil escribir métodos de extensión en Kotlin:
class A { }
class B {
fun A.newFunction() { ... }
}
Pero, ¿hay alguna manera de crear la variable de extensión? Me gusta:
class B {
var A.someCounter: Int = 0
}
No hay forma de agregar propiedades de extensión con campos de respaldo a las clases, porque las extensiones no modifican realmente una clase .
Solo puede definir una propiedad de extensión con un captador personalizado (y un configurador para var
) o una propiedad delegada .
usar la identidad, no
equals()
/hashCode()
, para almacenar realmente los valores para cada objeto, como lo haceIdentityHashMap
;no impide que los objetos clave se recojan (utilizando referencias débiles ), como hace
WeakHashMap
.
Desafortunadamente, no hay WeakIdentityHashMap
en JDK, por lo que debe implementar el suyo propio (o tomar una implementación completa ).
Luego, en función de esta asignación, puede crear una clase de delegado que satisfaga los requisitos de los delegados de propiedad . Aquí hay un ejemplo de implementación no segura para subprocesos:
class FieldProperty<R, T : Any>(
val initializer: (R) -> T = { throw IllegalStateException("Not initialized.") }
) {
private val map = WeakIdentityHashMap<R, T>()
operator fun getValue(thisRef: R, property: KProperty<*>): T =
map[thisRef] ?: setValue(thisRef, property, initializer(thisRef))
operator fun setValue(thisRef: R, property: KProperty<*>, value: T): T {
map[thisRef] = value
return value
}
}
Ejemplo de uso:
var Int.tag: String by FieldProperty { "$it" }
fun main(args: Array<String>) {
val x = 0
println(x.tag) // 0
val z = 1
println(z.tag) // 1
x.tag = "my tag"
z.tag = x.tag
println(z.tag) // my tag
}
Cuando se define dentro de una clase, la asignación se puede almacenar de forma independiente para las instancias de la clase o en un objeto delegado compartido:
private val bATag = FieldProperty<Int, String> { "$it" }
class B() {
var A.someCounter: Int by FieldProperty { 0 } // independent for each instance of B
var A.tag: String by bATag // shared between the instances, but usable only inside B
}
Además, tenga en cuenta que la identidad no está garantizada para los tipos primitivos de Java debido al boxeo.
Y sospecho que el rendimiento de esta solución es significativamente peor que el de los campos regulares, muy probablemente cerca del Map
normal, pero eso requiere más pruebas.
Para el soporte de propiedades anulables y la implementación segura de subprocesos, consulte here .
No puede agregar un campo, pero puede agregar una propiedad, que delega a otras propiedades / métodos del objeto para implementar su (s) accesor (es). Por ejemplo, suponga que desea agregar una propiedad secondsSinceEpoch
a la clase java.util.Date
, puede escribir
var Date.secondsSinceEpoch: Long
get() = this.time / 1000
set(value) {
this.time = value * 1000
}
No, la documentation explica esto:
Las extensiones no modifican realmente las clases que extienden. Al definir una extensión, no inserta nuevos miembros en una clase, sino que simplemente hace que las nuevas funciones se puedan llamar con la notación de puntos en las instancias de esta clase.
y
Tenga en cuenta que, dado que las extensiones en realidad no insertan miembros en las clases, no hay una forma eficiente de que una propiedad de extensión tenga un campo de respaldo. Es por esto que los inicializadores no están permitidos para las propiedades de extensión. Su comportamiento solo se puede definir proporcionando explícitamente a los getters / setters.
Pensar en las funciones / propiedades de extensión como simplemente azúcar sintáctica para llamar a una función estática y pasar un valor con suerte lo deja claro.
Puede crear una propiedad de extensión con getter y setter anulados:
var A.someProperty: Int
get() = /* return something */
set(value) { /* do something */ }
Pero no puede crear una propiedad de extensión con un campo de respaldo porque no puede agregar un campo a una clase existente.