Szukaj…
Wprowadzenie
Refleksja to zdolność języka do sprawdzania kodu w czasie wykonywania zamiast czasu kompilacji.
Uwagi
Refleksja to mechanizm introspekcji konstrukcji języka (klas i funkcji) w czasie wykonywania.
Podczas celowania w platformę JVM funkcje odbicia środowiska wykonawczego są dystrybuowane w osobnym kotlin-reflect.jar
JAR: kotlin-reflect.jar
. Odbywa się to w celu zmniejszenia rozmiaru środowiska wykonawczego, ograniczenia nieużywanych funkcji i umożliwienia kierowania na inne platformy (takie jak JS).
Odwoływanie się do klasy
Aby uzyskać odwołanie do obiektu KClass
reprezentującego niektóre klasy, użyj podwójnych dwukropków:
val c1 = String::class
val c2 = MyClass::class
Odwoływanie się do funkcji
Funkcje są pierwszorzędnymi obywatelami Kotlina. Możesz uzyskać odwołanie do niego za pomocą podwójnych dwukropków, a następnie przekazać je do innej funkcji:
fun isPositive(x: Int) = x > 0
val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]
Współdziałanie z odbiciem Java
Aby uzyskać obiekt Class
Java z KClass
Kotlina, użyj właściwości rozszerzenia .java
:
val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java
val c2: Class<MyClass> = MyClass::class.java
Ten ostatni przykład zostanie zoptymalizowany przez kompilator, aby nie przydzielał pośredniej instancji KClass
.
Uzyskiwanie wartości wszystkich właściwości klasy
Biorąc pod uwagę Example
klasy rozszerzającej klasę BaseExample
z pewnymi właściwościami:
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"
}
Można uzyskać wszystkie właściwości klasy:
val example = Example(field1 = "abc", field2 = 1, baseField = "someText")
example::class.memberProperties.forEach { member ->
println("${member.name} -> ${member.get(example)}")
}
Uruchomienie tego kodu spowoduje zgłoszenie wyjątku. Właściwość private val privateField
jest zadeklarowana jako prywatna i wywołanie na niej member.get(example)
nie powiedzie się. Jednym ze sposobów na poradzenie sobie z tym jest odfiltrowanie prywatnych nieruchomości. W tym celu musimy sprawdzić modyfikator widoczności gettera Java właściwości. W przypadku private val
wartości getter nie istnieje, więc możemy założyć prywatny dostęp.
Funkcja pomocnicza i jej użycie może wyglądać następująco:
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)}")
}
Innym podejściem jest udostępnienie prywatnych nieruchomości za pomocą odbicia:
example::class.memberProperties.forEach { member ->
member.isAccessible = true
println("${member.name} -> ${member.get(example)}")
}
Ustawianie wartości wszystkich właściwości klasy
Jako przykład chcemy ustawić wszystkie właściwości ciągu przykładowej klasy
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
}
}
}
Uzyskiwanie zmiennych właściwości opiera się na uzyskiwaniu wszystkich właściwości, filtrując zmienne właściwości według typu. Musimy także sprawdzić widoczność, ponieważ odczytywanie prywatnych właściwości powoduje wyjątek w czasie wykonywania.
val instance = TestClass()
TestClass::class.memberProperties
.filter{ prop.visibility == KVisibility.PUBLIC }
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
System.out.println("${prop.name} -> ${prop.get(instance)")
}
Aby ustawić wszystkie właściwości String
na "Our Value"
, możemy dodatkowo filtrować według typu zwracanego. Ponieważ Kotlin jest oparty na maszynie wirtualnej Java, działa usuwanie typu , a zatem właściwości zwracające typy ogólne, takie jak List<String>
będą takie same jak List<Any>
. Niestety odbicie nie jest złotą kulą i nie ma rozsądnego sposobu, aby tego uniknąć, więc musisz uważać w swoich przypadkach użycia.
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")
}