Buscar..


Introducción

Reflexión es la capacidad de un lenguaje para inspeccionar el código en tiempo de ejecución en lugar de compilarlo.

Observaciones

La reflexión es un mecanismo para realizar introspecciones de construcciones de lenguaje (clases y funciones) en el tiempo de ejecución.

Cuando se dirige a la plataforma JVM, las características de reflexión de tiempo de ejecución se distribuyen en JAR separado: kotlin-reflect.jar . Esto se hace para reducir el tamaño del tiempo de ejecución, cortar las funciones no utilizadas y hacer posible apuntar a otras plataformas (como JS).

Hacer referencia a una clase

Para obtener una referencia a un objeto KClass que representa alguna clase, use dos puntos dobles:

val c1 = String::class
val c2 = MyClass::class

Haciendo referencia a una función

Las funciones son ciudadanos de primera clase en Kotlin. Puede obtener una referencia en él usando dos puntos dobles y luego pasarlo a otra función:

fun isPositive(x: Int) = x > 0

val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]

Interoperación con la reflexión de Java.

Para obtener un objeto Class de Java de KClass de Kotlin, use la propiedad de extensión .java :

val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java

val c2: Class<MyClass> = MyClass::class.java

El compilador optimizará este último ejemplo para no asignar una instancia de KClass intermedia.

Obtención de valores de todas las propiedades de una clase.

Dada clase de Example extiende la clase BaseExample con algunas propiedades:

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

Uno puede hacerse con todas las propiedades de una clase:

val example = Example(field1 = "abc", field2 = 1, baseField = "someText")

example::class.memberProperties.forEach { member ->
    println("${member.name} -> ${member.get(example)}")
}

Ejecutar este código hará que se genere una excepción. La propiedad private val privateField se declara privada y llamar a member.get(example) no se realizará correctamente. Una forma de manejarlo es filtrar propiedades privadas. Para hacer eso, tenemos que verificar el modificador de visibilidad del Java getter de una propiedad. En el caso de private val el captador no existe, por lo que podemos asumir el acceso privado.

La función de ayuda y su uso podrían verse así:

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

Otro enfoque es hacer que las propiedades privadas sean accesibles mediante la reflexión:

example::class.memberProperties.forEach { member ->
    member.isAccessible = true
    println("${member.name} -> ${member.get(example)}")
}

Establecer valores de todas las propiedades de una clase

Como ejemplo, queremos establecer todas las propiedades de cadena de una clase de muestra

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

Obtener propiedades mutables se basa en obtener todas las propiedades, filtrando las propiedades mutables por tipo. También necesitamos verificar la visibilidad, ya que la lectura de propiedades privadas resulta en una excepción de tiempo de ejecución.

val instance = TestClass()
TestClass::class.memberProperties
        .filter{ prop.visibility == KVisibility.PUBLIC }
        .filterIsInstance<KMutableProperty<*>>()
        .forEach { prop ->
            System.out.println("${prop.name} -> ${prop.get(instance)")
        }

Para establecer todas las propiedades de String en "Our Value" , también podemos filtrar por el tipo de retorno. Dado que Kotlin se basa en Java VM, Type Erasure está en vigencia y, por lo tanto, las Propiedades que devuelven tipos genéricos como List<String> serán las mismas que List<Any> . Lamentablemente, la reflexión no es una bala de oro y no hay una forma sensata de evitar esto, por lo que debe tener cuidado en sus casos de uso.

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow