Reflection en el desarrollo de aplicaciones — qué es, mecanismos de reflexión y cómo aplicarlos

Autor: IT Sectr Publicado: 2026-05-17 Tiempo de lectura: 8 min

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 el acceso a los metadatos de clases, métodos y campos durante la ejecución del programa.
  • Java Reflection API proporciona las clases Class, Method, Field y Constructor para el análisis dinámico.
  • La reflexión de Kotlin usa KClass y KFunction, integradas con coroutines y serialización.
  • Objective-C Runtime es una forma de reflection mediante class_copyMethodList y objc_getClass.
  • El rendimiento de reflection es de 10 a 100 veces menor que las llamadas directas por la ausencia de optimizaciones JIT.

¿Qué es Reflection?

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.

Cómo funciona Reflection en Java y Kotlin

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.

kotlin
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.

Java Reflection: Class, Method, Field

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
// 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.

Objective-C Runtime: un modelo alternativo de reflexión

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.

Aplicación de Reflection en el desarrollo móvil

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.

  • Serialización — Gson lee los campos declarados de un objeto mediante Field.get() y crea JSON según las anotaciones @SerializedName.
  • Inyección de dependencias — Dagger genera código mediante annotation processing, Koin usa la reflection de Kotlin para la resolución en runtime.
  • Pruebas — JUnit encuentra métodos con @Test mediante reflection y los invoca; Mockito crea mocks mediante dynamic proxy.
  • Base de datos — Room comprueba los campos de Entity mediante Class.getDeclaredFields() en tiempo de compilación (a través de KAPT/KSP).
  • Analítica y monitorización — Firebase Crashlytics obtiene el stack trace mediante Throwable.getStackTrace(), basado en reflection.

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.

Rendimiento de Reflection: el coste del acceso dinámico

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ónLlamada directaA través de ReflectionRalentización
Llamada a un método sin parámetros~3 ns~120 ns40x
Lectura de un campo int~1 ns~85 ns85x
Llamada a un método con 2 parámetros~4 ns~250 ns62x
Creación de una instancia mediante el constructor~5 ns~180 ns36x
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.

Alternativas a Reflection: anotaciones y code generation

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.

kotlin
// 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.

Comparación de enfoques

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.

Limitaciones de Reflection en Android e iOS

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:

groovy
// 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

¿Es perjudicial Reflection para el rendimiento de la aplicación?

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.

¿En qué se diferencia la reflection de Java de la de Kotlin?

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.

¿Cómo evitar problemas con la ofuscación al usar Reflection?

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.

¿Existe Reflection en Swift?

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.

¿Qué bibliotecas usan Reflection en Android?

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

  • Reflection es un mecanismo de runtime para acceder a los metadatos de clases, métodos y campos.
  • La reflection de Java usa Class, Method, Field; Kotlin usa KClass, KFunction, KProperty con integración de coroutines.
  • Objective-C Runtime proporciona class_copyMethodList y objc_getClass sin restricciones de acceso.
  • Reflection es más lenta que la llamada directa de 10 a 100 veces por la ausencia de optimizaciones JIT.
  • Alternativas — code generation (KSP, KAPT) y annotation processing — eliminan la sobrecarga de reflection.
  • ProGuard/R8 requiere keep rules para las clases usadas mediante Class.forName() y getDeclaredMethod().
  • Reflection es indispensable para la carga dinámica de plugins, DI y frameworks de prueba donde los tipos se desconocen en tiempo de compilación.

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.

Discutir el proyecto

Lea también