Kotlin
Reflexión
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")
}