Kotlin的扩展领域

时间:2016-04-08 14:37:36

标签: kotlin

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

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

但是有没有办法创建扩展变量?像:

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

5 个答案:

答案 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>() }