Reflection (reflexión) es un mecanismo de runtime que permite al código inspeccionar su propia estructura: obtener clases, métodos, campos y anotaciones sin conocer los tipos en tiempo de compilación. Esta herramienta está en la base de muchos frameworks móviles: la serialización JSON (Gson, Moshi), la inyección de dependencias (Dagger, Koin) y los runners de pruebas (JUnit, XCTest). Según el Tutorial de Reflection de Oracle Java, 2024, reflection es un elemento obligatorio de la plataforma Java, utilizado por todas las grandes bibliotecas.
Puntos clave
Reflection es la capacidad de un programa de observar y modificar su propia estructura y comportamiento durante la ejecución. En los lenguajes orientados a objetos esto significa obtener objetos Class, Method, Field y Constructor, que representan los elementos del programa como datos disponibles para su lectura e invocación.
El término “reflection” se introdujo en la comunidad de inteligencia artificial en 1982 (Brian Cantwell Smith) y se implementó en el lenguaje Smalltalk. En el desarrollo móvil, reflection apareció por primera vez en Java ME y Objective-C (1986, NextStep). Hoy cada gran plataforma móvil tiene su propio API de reflection: Java/Kotlin para Android, Objective-C Runtime para iOS, Swift Mirror API para Swift.
El mecanismo de reflection se basa en los metadatos que el compilador guarda en el bytecode o en el binario. Android almacena información completa sobre las clases en archivos DEX, iOS en la sección __objc_classlist del segmento Mach-O. El runtime carga estos metadatos en memoria y proporciona un API para recorrerlos.
Java Reflection API se construye en torno a la clase java.lang.Class. Cualquier objeto en Java se puede convertir en Class mediante .getClass() o Class.forName(). Desde Class se extraen todos los métodos, campos, constructores, anotaciones y superclases. Kotlin hereda la reflection de Java y añade sus propias KClass, KFunction, KProperty del paquete kotlin.reflect.
import kotlin.reflect.full.declaredMemberFunctions
data class User(
val name: String,
val email: String
)
fun inspectClass() {
val kClass = User::class
val properties = kClass.declaredMemberProperties
val functions = kClass.declaredMemberFunctions
properties.forEach { prop ->
println("Propiedad: ${prop.name}, tipo: ${prop.returnType}")
}
}
En este ejemplo KClass proporciona los metadatos de la data class User. declaredMemberProperties devuelve una lista de propiedades con sus tipos y getters. La reflection de Kotlin está estrechamente integrada con las coroutines: KFunction soporta el modificador suspend, lo que permite llamar métodos asíncronos a través de reflection.
La reflection de Java trabaja con Class<?>, Method.setAccessible() y Field.get(). setAccessible(true) desactiva las comprobaciones de control de acceso del lenguaje Java para los elementos privados. Es un mecanismo potente pero peligroso: en Android a partir de API 28, llamar a setAccessible sobre métodos ocultos del sistema puede provocar InaccessibleObjectException.
// Java reflection: invocación de un método privado
Class> clazz = Class.forName("com.example.MyClass");
Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("privateMethod", String.class);
method.setAccessible(true);
method.invoke(instance, "reflection test");
El código demuestra Class.forName(): la carga dinámica de una clase por nombre de cadena. Esta es la base de las arquitecturas de plugins: una clase puede ser desconocida en tiempo de compilación, pero cargada y ejecutada mediante reflection en runtime. getDeclaredMethod(“privateMethod”, ...) encuentra un método por nombre y tipos de parámetros, e invoke lo ejecuta.
El runtime de Objective-C proporciona las funciones class_copyMethodList, class_copyPropertyList, objc_getAssociatedObject. A diferencia de Java, Objective-C no oculta los métodos privados por defecto: el runtime ve todos los métodos de la clase. Esto explica por qué el method swizzling funciona sin setAccessible: el runtime no tiene encapsulación a nivel de metadatos.
Reflection se utiliza en las bibliotecas clave del desarrollo móvil. La serialización JSON (Gson, Moshi, Kotlinx.serialization) obtiene las propiedades del objeto mediante reflection y las compara con las claves JSON. La inyección de dependencias (Dagger, Koin, Swinject) analiza constructores y campos para la inyección automática de dependencias. Las bibliotecas ORM (Room, Realm) usan reflection para mapear las clases a tablas de la base de datos.
Cada una de estas aplicaciones funciona precisamente en runtime: el código no sabe de antemano con qué clases se encontrará. Reflection proporciona un mecanismo universal para superar esta incertidumbre a costa del rendimiento y la seguridad.
Reflection es de 10 a 100 veces más lenta que las llamadas directas a métodos. La causa es la falta de optimizaciones JIT (devirtualization, inlining), la comprobación de tipos en cada llamada y el empaquetado de parámetros en Object[]/varargs. ART en Android 14 no puede optimizar con inline las llamadas de reflection porque el método objetivo no se conoce hasta el momento de la ejecución.
| Operación | Llamada directa | A través de Reflection | Ralentización |
|---|---|---|---|
| Llamada a un método sin parámetros | ~3 ns | ~120 ns | 40x |
| Lectura de un campo int | ~1 ns | ~85 ns | 85x |
| Llamada a un método con 2 parámetros | ~4 ns | ~250 ns | 62x |
| Creación de una instancia mediante el constructor | ~5 ns | ~180 ns | 36x |
| Resolución de una clase por cadena | — | ~800 ns | — |
Los datos se obtuvieron en un Google Pixel 8 (Android 14, ART). El rendimiento de reflection mejora con cada versión de Android: en Android 9 una llamada mediante Method.invoke() era 150 veces más lenta que la directa, en Android 14 es 40 veces. ART usa mecanismos integrados de method handle para la optimización.
Para las secciones críticas de rendimiento, los desarrolladores sustituyen reflection por code generation: Dagger usa annotation processing en lugar de la búsqueda en runtime, Kotlinx.serialization genera serializadores mediante KSP, Moshi adapta @JsonClass(generateAdapter = true) para codegen en tiempo de compilación.
Annotation processing (KAPT, KSP) y code generation son las principales alternativas a reflection en el desarrollo móvil. Trasladan el análisis de metadatos del runtime al tiempo de compilación: el código se genera antes de iniciar la aplicación, lo que elimina la sobrecarga de reflection y mejora el rendimiento.
// KSP: code generation en lugar de reflection
@Serializable
data class Config(
val apiUrl: String,
val timeout: Int
)
// KSP genera ConfigSerializer sin reflection
fun loadConfig(json: String): Config {
return Config.serializer().decodeFromString(json)
}
En este ejemplo @Serializable es la anotación de Kotlinx.serialization. KSP (Kotlin Symbol Processing) analiza el código fuente en tiempo de compilación, encuentra todas las clases @Serializable y genera los serializadores. Durante la ejecución de la aplicación no se usa reflection: el serializador ya está compilado en código máquina.
Code generation ofrece mejor rendimiento, seguridad de tipos y un tamaño de binario menor (dead code elimination elimina las dependencias de reflection no utilizadas). Reflection sigue siendo necesaria para tareas en las que los tipos se desconocen en tiempo de compilación: carga dinámica de plugins, proxies en runtime, instrumentación de pruebas. Según Kotlin, Kotlinx.serialization con KSP es de 3 a 5 veces más rápida que Gson, que se basa en reflection.
Reflection en las plataformas móviles tiene limitaciones de seguridad y rendimiento. Android a partir de API 28 (Pie) restringe setAccessible para las interfaces non-SDK: intentar abrir un método oculto del sistema provoca una excepción o una advertencia. iOS con Swift no soporta reflection en el sentido clásico: Swift Mirror API solo permite la lectura de propiedades (name, value) sin modificación ni llamadas a métodos.
Google Play rechaza las aplicaciones que usan reflection para saltarse las restricciones de la plataforma: sustituir servicios del sistema, modificar políticas de SELinux, leer permisos protegidos. Apple también bloquea las aplicaciones que llaman a APIs privados mediante reflection: la revisión de App Review escanea el binario buscando firmas de cadena de objc_msgSend con selectores privados conocidos.
ProGuard/R8 es otra limitación. La ofuscación y la minificación del código renombran clases y métodos a nombres cortos (a, b, c). Si el código usa Class.forName(“com.example.MyClass”), se romperá tras la ofuscación. La solución son las keep rules en proguard-rules.pro:
// Reglas keep de ProGuard para reflection
-keep class com.example.** { *; }
-keep class * implements java.io.Serializable { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName ;
}
-keepattributes Signature, InnerClasses, EnclosingMethod
Las reglas -keep indican a R8 que no renombre las clases usadas mediante reflection. Sin estas reglas, una aplicación ofuscada fallará con ClassNotFoundException: el runtime no podrá encontrar la clase por el nombre de cadena que ha cambiado.
Preguntas frecuentes
Sí, reflection es de 10 a 100 veces más lenta que una llamada directa. Las razones principales: falta de optimizaciones JIT (inlining, devirtualization), empaquetado de parámetros y comprobación de tipos en cada llamada. Para el código de producción se recomienda sustituir reflection por code generation mediante KSP o annotation processing.
La reflection de Java funciona mediante Class, Method, Field y requiere setAccessible para los miembros privados. La reflection de Kotlin usa KClass, KFunction, KProperty y soporta sealed class, data class, coroutines (funciones suspend) y null-safety. La reflection de Kotlin se basa en la de Java, pero añade un API type-safe.
Añadir keep rules de ProGuard/R8 para clases, métodos y campos usados mediante reflection. Para cada Class.forName(), getDeclaredMethod(), getDeclaredField() debe existir una directiva -keep correspondiente. Herramientas como GreenDAO y Room generan keep rules automáticamente.
Swift no tiene reflection en sentido pleno. Mirror API (Swift 2+) permite leer las propiedades de una estructura o clase: nombre, valor, tipo. No es posible llamar métodos, modificar campos ni crear instancias por tipo. Para ello se usa Objective-C Runtime al heredar de NSObject con @objc dynamic.
Gson (serialización JSON), Retrofit (creación de implementaciones de interfaces mediante dynamic proxy), Mockito (creación de mocks), Koin (inyección de dependencias), Room (verificación de Entity en tiempo de compilación mediante KAPT), Firebase Crashlytics (análisis de stack trace). La mayoría de las bibliotecas migran a code generation con KSP/KAPT.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también