首页 文章

Kotlin的扩展领域

提问于
浏览
29

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

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

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

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

4 回答

  • 11

    不 - documentation解释了这个:

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

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

    考虑扩展函数/属性作为调用静态函数和传入值的语法糖有希望使这一点清楚 .

  • 30

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

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

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

  • 38

    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 .

  • 7

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

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

相关问题