Reflexión En Kotlin

En este tutorial aprenderás sobre el uso de la reflexión en Kotlin con el propósito de explorar la estructura tu código en tiempo de ejecución. Verás cómo obtener información sobre las declaraciones de tus clases, funciones y propiedades a través de las clases de la API de reflexión de Kotlin.

API Para Reflexión En Kotlin

La reflexión es la capacidad de un programa de analizar la estructura de sus entidades de código en tiempo de ejecución.

Para hacerla posible, Kotlin te provee una API de reflexión definida en el paquete kotlin.reflect que te facilita el acceso dinámico a propiedades y métodos de objetos en tiempo de ejecución, sin conocer de antemano dichas declaraciones.

Usar la API de reflexión requiere incluir la siguiente dependencia Gradle en tu proyecto IntelliJ IDEA:

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.32")
}

Con ese eso ya podrás usar las abstracciones de la API de reflexión para examinar clases, funciones y propiedades como verás a continuación.

Referencias De Clases

Para obtener la referencia de una clase usa la sintaxis Clase::class. El resultado será una instancia de KClass, la cual contiene propiedades, métodos y funciones de extensión que te posibilitan conocer todas las declaraciones en la clase.

Ejemplo: Imprimir los nombres de todas las propiedades de la clase InvoiceLine.

import kotlin.reflect.full.memberProperties

class InvoiceLine(val description: String, val price: Double, val quantity: Int) {
    val total get() = price * quantity
}

fun main() {
    val kClass = InvoiceLine::class
    kClass.memberProperties.forEach { println(it.name) }
}

Salida:

description
price
quantity
total

Si quieres obtener la clase de una instancia de la clase, la sintaxis es exactamente igual: objeto::class.

fun main() {
    val carrotsLine = InvoiceLine("Zanahoria", 1.0, 3)
    val lineClass = carrotsLine::class
    println(lineClass.constructors.first())
}

Salida:

fun <init>(kotlin.String, kotlin.Double, kotlin.Int): InvoiceLine

Referencias De Funciones

Las referencias de funciones son representadas por la clase KFunction. Y como habíamos visto en el tutorial de tipos función, estas son tratadas como valores y ser pasadas como tal.

Usa la expresión ::nombreFuncion para acceder a su referencia y pasarla en lugares que permitan literales de función.

Ejemplo: Crear una función que determine si un carácter es una vocal y pasar su referencia en una función filterNot() que se aplica en una palabra.

fun isVowel(char: Char): Boolean {
    return when (char) {
        'a', 'e', 'i', 'o', 'u' -> true
        'A', 'E', 'I', 'O', 'U' -> true
        else -> false
    }
}

fun main() {
    val word = "Tutoriales De Kotlin en Develou"
    val wordWithoutVowels = word.filterNot(::isVowel)
    println(wordWithoutVowels)
}

Salida:

Ttrls D Ktln n Dvl

En el caso de los métodos de una clase o funciones de extensión, antepón el nombre de la clase de la forma Clase::funcion.

Ejemplo: Reescribir la función isVowel() como una función de extensión y actualizar la referencia en el filtro.

fun Char.isVowel(): Boolean {
    return when (this) {
        'a', 'e', 'i', 'o', 'u' -> true
        'A', 'E', 'I', 'O', 'U' -> true
        else -> false
    }
}

fun main() {
    val word = "Tutoriales De Kotlin en Develou"
    val wordWithoutVowels = word.filterNot(Char::isVowel)
    println(wordWithoutVowels)
}

Referencias De Propiedades

Las propiedades también tienen una clase que representa su capacidad de introspección llamada KProperty. Puedes acceder a instancias de este tipo con el operador ::nombrePropiedad.

Kproperty te provee los métodos set() y get() para modificar y consultar los valores de las referencias. También puedes usar name para leer el nombre de la propiedad.

var sum = 0
val times = 4
fun main() {
    ::sum.set(::times.get())
    println("${::sum.name} = $sum")
}

Salida:

sum = 4

Usar reflexión para las propiedades miembros de clases también es de utilidad cuando pasamos una función lambda, cuyo cuerpo permite valores simples.

Ejemplo: Imprimir los nombres de una lista de productos.

class Product(val name: String, val price: Double)

fun main() {
    val products = listOf(
        Product("Zanahoria", 1.0),
        Product("Tomates", 2.1),
        Product("Harina", 3.0),
        Product("Frijoles", 2.5),
    )

    val productNames = products.map(Product::name)
    println(productNames)
}

Salida:

[Zanahoria, Tomates, Harina, Frijoles]

Usando la referencia de Product::name, la función map() interpreta que solo deseamos producir como valor final, a los nombres de los productos. Lo que es equivalente a pasar la lambda { it.name }.

¿Ha sido útil esta publicación?