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")
        }


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow