Kotlin 的推广领域

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

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

但是有没有什么方法可以创建扩展变量呢? 比如:

class B {
var A.someCounter: Int = 0
}
37018 次浏览

不,文件解释道:

扩展实际上并不修改它们所扩展的类。通过定义扩展,您不需要向类中插入新成员,只需要使用该类的实例上的点符号来调用新函数即可。

还有

请注意,由于扩展实际上不会将成员插入到类中,因此扩展属性没有有效的方法来拥有后备字段。这就是为什么扩展属性不允许使用初始值设定项。它们的行为只能通过显式提供 getters/setter 来定义。

将扩展函数/属性看作是调用静态函数并传入值的语法糖,这样就可以很好地解决这个问题。

然而,如果你 真的,真的想要做这样的事情..。

如上所述,关于效率,一个额外的支持字段直接添加到类是最好的方式来存储数据不可派生的现有非私有成员从类。但是,如果您不能控制类的实现,并且坚持要创建一个可以存储新数据的新属性,那么使用单独的外部表来实现 可以的方式就不会像 效率低下那样糟糕。使用一个单独的映射来键入这个类的对象实例,其值直接映射到您想要添加的值,然后为这个属性定义一个扩展 getter 和/或 setter,它使用您的外部表来存储与每个实例关联的数据。

val externalMap = mutableMapOf<ExistingClass, Int>()


var ExistingClass.newExtensionProperty : Int
get() = externalMap[this] ?: 0
set(value:Int) { externalMap[this] = value }

额外的映射查找将耗费您的成本——您需要考虑内存泄漏,或者使用适当的 GC 感知类型,但它确实有效。

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

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

但是不能创建带有后备字段的扩展属性,因为不能向现有类添加字段。

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

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

类有 无法添加带有后台字段的扩展属性,因为扩展 实际上不修改类

您只能使用自定义 getter (以及 var的 setter)或 授权财产来定义扩展属性。


但是,如果您需要定义一个扩展属性(其中 会表现得好像是一个备份字段) ,那么委托属性就很方便了。 其思想是创建一个属性委托来存储 对象到价值(object-to-value)映射:

  • 使用标识,而不是 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,但是这需要进一步的测试。

有关可空属性支持和线程安全实现,请参阅 给你

如果你正在扩展视图,你可以很容易地这样做..。 下面是我如何在 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!!
}

在需要查看扩展的地方使用它:

val EditText.eventClear get() = tagProperty(R.id.EditTextEventOnClearTagKey) { event<Unit>() }