Kotlin的扩展字段

在Kotlin中编写扩展方法很容易:

class A { } class B { fun A.newFunction() { ... } } 

但有没有办法创建扩展变量? 喜欢:

 class B { var A.someCounter: Int = 0 } 

否 – 文档解释了这一点:

扩展实际上并不修改它们扩展的类。 通过定义扩展,不要将新成员插入到类中,而只需使用此类的实例上的点符号来调用新函数。

请注意,由于扩展实际上并没有将成员插入到类中,所以扩展属性没有有效的方法来拥有后台字段。 这就是为什么初始化器不允许扩展属性。 他们的行为只能通过明确提供getters / setter来定义。

将扩展函数/属性作为调用一个静态函数的语法糖和传递一个值的想法有希望使这个清楚。

您可以使用重写的getter和setter创建扩展属性:

 var A.someProperty: Int get() = /* return something */ set(value) { /* do something */ } 

但是,您不能使用后台字段创建扩展属性,因为您无法将字段添加到现有类中。

没有办法将扩展属性与支持字段添加到类中,因为扩展并不实际修改类 。

您只能使用自定义getter(以及var setter)或委托属性来定义扩展属性 。


然而,如果你需要定义一个扩展属性,它的行为就像拥有一个后台字段一样,委托属性就派上用场了。 这个想法是创建一个属性委托来存储对象到值的映射:

  • 使用身份而不是equals() / hashCode()来实际存储每个对象的值,比如IdentityHashMap ;

  • 不会阻止关键对象被垃圾收集(使用弱引用 ),就像WeakHashMap一样。

不幸的是,JDK中没有WeakIdentityHashMap ,所以你必须实现你自己的(或者是一个完整的实现 )。

然后,根据这个映射,你可以创建一个满足属性委托需求的委托类。 以下是非线程安全实现的示例:

 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的原始类型不能保证身份。

我怀疑这个解决方案的性能比常规字段的性能要差得多,很可能接近法线Map ,但是这需要进一步的测试。

对于可空属性的支持和线程安全的实现请参考这里 。

您不能添加字段,但可以添加一个属性,该属性委托给对象的其他属性/方法来实现其访问者。 例如,假设你想为java.util.Date类添加一个secondsSinceEpoch属性,你可以写

 var Date.secondsSinceEpoch: Long get() = this.time / 1000 set(value) { this.time = value * 1000 }