Kotlin的扩展领域

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(和var的setter)或委托属性定义扩展属性。但是,如果您需要定义一个扩展属性,它的行为就像它有一个支持字段一样,那么委托属性就派上用场了。其想法是创建一个属性委托,用于存储对象到值的映射:

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

  • 不会阻止关键对象被垃圾收集(使用弱引用),如WeakHashMap所做的。

不幸的是,JDK中没有WeakIdentityHashMap,因此您必须实现自己的(或采取完整的实现)。

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

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

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

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


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

1
2
3
4
5
var Date.secondsSinceEpoch: Long
    get() = this.time / 1000
    set(value) {
        this.time = value * 1000
    }