Kotlin的扩展领域

Let*_*far 42 kotlin

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

class A { }
class B {
    fun A.newFunction() { ... }
}
Run Code Online (Sandbox Code Playgroud)

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

class B {
    var A.someCounter: Int = 0
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 47

不 - 文档说明了这一点:

扩展实际上并不修改它们扩展的类.通过定义扩展,您不会将新成员插入到类中,而只是在此类的实例上使用点符号使新函数可调用.

请注意,由于扩展实际上并未将成员插入到类中,因此扩展属性没有有效的方法来获得支持字段.这就是扩展属性不允许初始值设定项的原因.他们的行为只能通过明确提供getter/setter来定义.

考虑扩展函数/属性只是用于调用静态函数和传入值的语法糖,这有助于明确这一点.

  • 对于大多数用途来说,额外的映射查找并不那么昂贵,但是显示的代码将停止“ExistingClass”的垃圾收集(“externalMap”中的所有实例永远不会被垃圾收集)。这是主要的内存泄漏,永远不应该出现在可接受的答案中。使用“WeakReference”或“WeakHashMap”将是朝着正确方向迈出的一步(但不是多平台)。 (5认同)

Mic*_*ael 41

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

var A.someProperty: Int
  get() = /* return something */
  set(value) { /* do something */ }
Run Code Online (Sandbox Code Playgroud)

但是您无法使用支持字段创建扩展属性,因为您无法将字段添加到现有类.


hot*_*key 18

没有办法与支持字段添加扩展属性上课,因为扩展实际上并不修改类.

您只能使用自定义getter(和setter for var)或委托属性定义扩展属性.


但是,如果您需要定义一个扩展属性,其行为就像它具有支持字段一样,则委派属性会派上用场.我们的想法是创建一个存储对象到值映射的属性委托:

  • 使用标识,而不是equals()/ hashCode(),实际存储每个对象的值,就像IdentityHashMap;

  • 不防止密钥对象被垃圾收集(使用弱引用),就像这样WeakHashMap做.

不幸的是,WeakIdentityHashMap在JDK中没有,所以你必须实现自己的(或采取完整的实现).

然后,基于此映射,您可以创建满足属性委托要求的委托类.这是一个非线程安全实现的示例:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

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
}
Run Code Online (Sandbox Code Playgroud)

在类中定义时,映射可以独立存储在类的实例中,也可以存储在共享的委托对象中:

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
}
Run Code Online (Sandbox Code Playgroud)

此外,请注意,由于装箱,不保证 Java的原始类型的身份.

我怀疑这个解决方案的性能明显比常规字段差,很可能接近正常Map,但这需要进一步测试.

有关可空属性支持和线程安全实现,请参阅此处.


JB *_*zet 9

您不能添加字段,但可以添加一个属性,该属性委托给对象的其他属性/方法以实现其访问器.例如,假设您要向类中添加secondsSinceEpoch属性,则java.util.Date可以编写

var Date.secondsSinceEpoch: Long 
    get() = this.time / 1000
    set(value) {
        this.time = value * 1000
    }
Run Code Online (Sandbox Code Playgroud)