在Kotlin中编写扩展方法很容易:
class A { }
class B {
fun A.newFunction() { ... }
}
但是有没有办法创建扩展变量?像:
class B {
var A.someCounter: Int = 0
}
答案 0 :(得分:42)
不 - documentation解释了这个:
扩展实际上并不修改它们扩展的类。通过定义扩展,您不会将新成员插入到类中,而只是在该类的实例上使用点符号使新函数可调用。
和
请注意,由于扩展实际上并未将成员插入到类中,因此扩展属性没有有效的方法来获得支持字段。这就是扩展属性不允许初始值设定项的原因。他们的行为只能通过明确提供getter / setter来定义。
将扩展函数/属性视为用于调用静态函数和传递值的语法糖,希望能够明确这一点。
答案 1 :(得分:37)
您可以使用重写的getter和setter创建扩展属性:
var A.someProperty: Int
get() = /* return something */
set(value) { /* do something */ }
但是您无法使用支持字段创建扩展属性,因为您无法将字段添加到现有类。
答案 2 :(得分:15)
有no way to add extension properties with backing fields个班级,因为分机do not actually modify a class。
您只能使用自定义getter(以及var
的setter)或delegated property定义扩展属性。
使用身份而不是equals()
/ hashCode()
来实际存储每个对象的值,例如IdentityHashMap
;
不会阻止键对象被垃圾回收(使用weak references),就像WeakHashMap
那样。
不幸的是,JDK中没有WeakIdentityHashMap
,所以你必须实现自己的(或者complete implementation)。
然后,根据此映射,您可以创建一个满足property delegates requirements的委托类。这是一个非线程安全实现的示例:
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
}
}
用法示例:
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
}
在类中定义时,映射可以独立存储于类的实例或共享委托对象中:
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
}
此外,请注意由于装箱而导致Java的原始类型的身份is not guaranteed。
我怀疑这个解决方案的性能明显比常规字段差,很可能接近正常Map
,但这需要进一步测试。
有关可空属性支持和线程安全实现,请参阅here。
答案 3 :(得分:8)
您无法添加字段,但可以添加属性,该属性委托给对象的其他属性/方法以实现其访问者。例如,假设您要向secondsSinceEpoch
类添加java.util.Date
属性,可以编写
var Date.secondsSinceEpoch: Long
get() = this.time / 1000
set(value) {
this.time = value * 1000
}
答案 4 :(得分:0)
如果要扩展View,可以像这样很容易地做到这一点... 这是我如何在EditText类扩展中创建一些自定义类Event属性的示例:
定义密钥的ID:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="EditTextEventOnClearTagKey" type="id" />
</resources>
这样定义一个可重用的扩展名:
fun <T : Any> View.tagProperty(@IdRes key: Int, onCreate: () -> T): T {
@Suppress("UNCHECKED_CAST")
var value = getTag(key) as? T
if (value.isNull) {
value = onCreate()
setTag(key, value)
}
return value!!
}
在所需的View扩展程序中使用它:
val EditText.eventClear get() = tagProperty(R.id.EditTextEventOnClearTagKey) { event<Unit>() }