Extension fields in Kotlin
在Kotlin中编写扩展方法很容易:
1 2 3 4 | class A { } class B { fun A.newFunction() { ... } } |
但是,有什么方法可以创建扩展变量吗?像:
1 2 3 | class B { var A.someCounter: Int = 0 } |
不-文档解释了这一点:
Extensions do not actually modify classes they extend. By defining an extension, you do not insert new members into a class, but merely make new functions callable with the dot-notation on instances of this class.
和
Note that, since extensions do not actually insert members into classes, there’s no efficient way for an extension property to have a backing field. This is why initializers are not allowed for extension properties. Their behavior can only be defined by explicitly providing getters/setters.
把扩展函数/属性看作是调用静态函数和传递值的语法糖,这很有希望说明这一点。
可以使用重写的getter和setter创建扩展属性:
1 2 3 | var A.someProperty: Int get() = /* return something */ set(value) { /* do something */ } |
但是不能用支持字段创建扩展属性,因为不能将字段添加到现有类中。
无法向类添加带有支持字段的扩展属性,因为扩展实际上并不修改类。
只能使用自定义getter(和
使用身份而不是
equals() /hashCode() 来实际存储每个对象的值,就像IdentityHashMap 那样;不会阻止关键对象被垃圾收集(使用弱引用),如
WeakHashMap 所做的。
不幸的是,JDK中没有
然后,基于此映射,您可以创建一个满足属性委托要求的委托类。下面是一个非线程安全实现示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 } } |
使用实例:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 } |
当在类内定义时,映射可以独立地存储在类的实例或共享委托对象中:
1 2 3 4 5 6 | 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的原始类型不能保证身份。
我怀疑这个解决方案的性能明显比常规字段差,很可能接近正常的
有关可以为空的属性支持和线程安全实现,请参阅此处。
不能添加字段,但可以添加属性,该属性委托给对象的其他属性/方法以实现其访问器。例如,假设您想将一个
1 2 3 4 5 | var Date.secondsSinceEpoch: Long get() = this.time / 1000 set(value) { this.time = value * 1000 } |