Расширение полей в Котлине
Легко писать методы расширения в Котлине:
class A { }
class B {
fun A.newFunction() { ... }
}
Но есть ли способ создать переменную расширения? Как:
class B {
var A.someCounter: Int = 0
}
Ответы
Ответ 1
Нет - документация объясняет это:
Расширения фактически не изменяют классы, которые они распространяют. Определяя расширение, вы не вставляете новые члены в класс, а просто делаете новые функции вызываемыми с помощью точечной нотации для экземпляров этого класса.
и
Обратите внимание, что, поскольку расширения фактически не вставляют элементы в классы, нет эффективного способа для свойства расширения иметь фоновое поле. Вот почему инициализаторы не допускаются для свойств расширения. Их поведение может быть определено только путем явного предоставления getters/setters.
Мысль о функциях/свойствах расширения как просто синтаксического сахара для вызова статической функции и передачи в ценности, надеюсь, делает это ясным.
Ответ 2
Вы можете создать свойство расширения с переопределенным getter и setter:
var A.someProperty: Int
get() = /* return something */
set(value) { /* do something */ }
Но вы не можете создать свойство расширения с помощью поля поддержки, потому что вы не можете добавить поле в существующий класс.
Ответ 3
не добавлять свойства расширения с полями поддержки в классы, поскольку расширения фактически не изменяют класс.
Вы можете определить свойство расширения с помощью настраиваемого getter (и setter для var
) или делегированное свойство.
Однако, если вам нужно определить свойство расширения, которое будет вести себя так, как если бы оно имело поле поддержки, делегированные свойства пригождаются.
Идея состоит в том, чтобы создать делегат свойств, который будет хранить сопоставление объектов со значением:
-
используя идентификатор, а не equals()
/hashCode()
, чтобы фактически хранить значения для каждого объекта, например IdentityHashMap
does;
-
не препятствует сбору объектов-объектов (используя слабые ссылки), например 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
, но это требует дальнейшего тестирования.
Для поддержки свойств NULL и потокобезопасной реализации см. здесь.
Ответ 4
Вы не можете добавить поле, но вы можете добавить свойство, которое делегирует другим свойствам/методам объекта для реализации своих аксессуаров. Например, предположим, что вы хотите добавить свойство secondsSinceEpoch
в класс java.util.Date
, вы можете написать
var Date.secondsSinceEpoch: Long
get() = this.time / 1000
set(value) {
this.time = value * 1000
}