Kotlin
отражение
Поиск…
Вступление
Отражение - это способность языка проверять код во время выполнения, а не на время компиляции.
замечания
Отражение - это механизм для интроспекции языковых конструкций (классов и функций) во время выполнения.
При таргетинге на платформу JVM функции отображения времени выполнения распределяются в отдельном JAR: kotlin-reflect.jar
. Это делается для уменьшения размера исполняемого файла, сокращения неиспользуемых функций и возможности для таргетинга на другие (например, JS) платформы.
Ссылка на класс
Чтобы получить ссылку на объект KClass
представляющий некоторый класс, используйте двойные двоеточия:
val c1 = String::class
val c2 = MyClass::class
Ссылка на функцию
Функции - первоклассные граждане в Котлине. Вы можете получить ссылку на нее, используя двойные двоеточия, а затем передать ее другой функции:
fun isPositive(x: Int) = x > 0
val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]
Взаимодействие с отражением Java
Чтобы получить объект Class
Java от KClass
Kotlin, используйте свойство расширения .java
:
val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java
val c2: Class<MyClass> = MyClass::class.java
Последний пример будет оптимизирован компилятором, чтобы не выделять промежуточный экземпляр KClass
.
Получение значений всех свойств класса
Данный Example
класса, расширяющий класс BaseExample
с некоторыми свойствами:
open class BaseExample(val baseField: String)
class Example(val field1: String, val field2: Int, baseField: String):
BaseExample(baseField) {
val field3: String
get() = "Property without backing field"
val field4 by lazy { "Delegated value" }
private val privateField: String = "Private value"
}
Можно получить все свойства класса:
val example = Example(field1 = "abc", field2 = 1, baseField = "someText")
example::class.memberProperties.forEach { member ->
println("${member.name} -> ${member.get(example)}")
}
Запуск этого кода вызовет исключение. Свойство private val privateField
объявляется приватным, а вызов member.get(example)
на нем не будет выполнен. Один из способов справиться с этим - отфильтровать частные свойства. Для этого нам нужно проверить модификатор видимости Java getter. В случае private val
геттер не существует, поэтому мы можем принять частный доступ.
Вспомогательная функция и ее использование могут выглядеть так:
fun isFieldAccessible(property: KProperty1<*, *>): Boolean {
return property.javaGetter?.modifiers?.let { !Modifier.isPrivate(it) } ?: false
}
val example = Example(field1 = "abc", field2 = 1, baseField = "someText")
example::class.memberProperties.filter { isFieldAccessible(it) }.forEach { member ->
println("${member.name} -> ${member.get(example)}")
}
Другой подход заключается в том, чтобы сделать частные свойства доступными с использованием рефлексии:
example::class.memberProperties.forEach { member ->
member.isAccessible = true
println("${member.name} -> ${member.get(example)}")
}
Установка значений всех свойств класса
В качестве примера мы хотим установить все свойства строки класса образца
class TestClass {
val readOnlyProperty: String
get() = "Read only!"
var readWriteString = "asd"
var readWriteInt = 23
var readWriteBackedStringProperty: String = ""
get() = field + '5'
set(value) { field = value + '5' }
var readWriteBackedIntProperty: Int = 0
get() = field + 1
set(value) { field = value - 1 }
var delegatedProperty: Int by TestDelegate()
private var privateProperty = "This should be private"
private class TestDelegate {
private var backingField = 3
operator fun getValue(thisRef: Any?, prop: KProperty<*>): Int {
return backingField
}
operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: Int) {
backingField += value
}
}
}
Получение изменчивых свойств основывается на получении всех свойств, фильтрации изменяемых свойств по типу. Нам также необходимо проверить видимость, так как чтение частных свойств приводит к исключению времени выполнения.
val instance = TestClass()
TestClass::class.memberProperties
.filter{ prop.visibility == KVisibility.PUBLIC }
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
System.out.println("${prop.name} -> ${prop.get(instance)")
}
Чтобы установить все свойства String
в "Our Value"
мы можем дополнительно фильтровать тип возвращаемого значения. Поскольку Kotlin основан на Java VM, тип Erasure действует, и, таким образом, свойства, возвращающие общие типы, такие как List<String>
будут такими же, как List<Any>
. Печальное отражение - не золотая пуля, и нет разумного способа избежать этого, поэтому вам нужно следить в своих случаях использования.
val instance = TestClass()
TestClass::class.memberProperties
.filter{ prop.visibility == KVisibility.PUBLIC }
// We only want strings
.filter{ it.returnType.isSubtypeOf(String::class.starProjectedType) }
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
// Instead of printing the property we set it to some value
prop.setter.call(instance, "Our Value")
}