«En informática, reflexión (o reflexión computacional) es la capacidad que tiene un programa de ordenador para observar y opcionalmente modificar su estructura de alto nivel.»
Esta es la definición que aparece en wikipedia en referencia al concepto de reflexión en el mundo de la informática y la programación.
Es una definición un tanto abstracta, pero que nos permite intuir en qué consiste todo esto de la reflexión.
Siendo más concretos y prácticos, el uso de la reflexión a nivel de programación, nos permitirá, entre muchas otras cosas, crear instancias u objetos de dinámicamente de tipos concretos a partir del nombre de su clase. Podremos además, acceder a la información de los objetos, conociendo y/o ejecutando sus atributos y métodos públicos; y todo ello en tiempo de ejecución.
Vamos a plantear un pequeño ejemplo práctico de introducción al uso de reflexión en el lenguaje de programación Java.
Imaginemos la siguiente estructura de clases definida en el diagrama UML que se muestra a continuación.
Reflexión en Java
Definiremos en primer lugar una clase abstracta denominada Vehicle. Esta clase padre define 2 métodos abstractos (acelerar/speedUp y frenar/brake) que deberán ser implementados por todas aquellas clases hijas que deseen heredar de Vehicle.
Clase (padre) Vehicle
package es.v3.test.reflection; /** * Clase abstracta padre * @author v3rgu1.com */ public abstract class Vehicle { public String name; public abstract void speedUp(); public abstract void brake(); }//Vehicle
A continuación crearemos 2 clases hijas que implementarán la clase abstracta padre Vehicle
Clase (hija) Car
package es.v3.test.reflection; /** * Clase hija que hereda de Vehicle y representa un vehículo de tipo coche. * @author v3rgu1.com */ public class Car extends Vehicle { @Override public void speedUp() { System.out.println("Me llamo " + this.name + " y soy un coche que está acelerando"); } @Override public void brake() { System.out.println("Me llamo " + this.name + " y soy un coche que está frenando"); } }//Car
Clase (hija) Motorcycle
package es.v3.test.reflection; /** * Clase hija que hereda de Vehicle y representa un vehículo de tipo motocicleta. * @author v3rgu1.com */ public class Motorcycle extends Vehicle { @Override public void speedUp() { System.out.println("Me llamo " + this.name + " y soy una moto que está acelerando"); } @Override public void brake() { System.out.println("Me llamo " + this.name + " y soy una moto que está frenando"); } }//Motorcycle
Una vez creadas estas 3 clases con las que llevaremos a cabo nuestros próximos ejemplos relacionados con la reflexión en programación, vamos a crear una clase ejecutable (main) con la que crearemos instancias en tiempo de ejecución de las clases hijas definidas anteriormente. Recuperaremos además la información de cada una de ellas y ejecutaremos sus métodos públicos sin previo conocimiento de ellos.
Pasaremos 2 argumentos a nuestra clase ejecutable por línea de comandos: el nombre de la clase a instanciar («Car» o «Motorcycle») y el nombre de nuestro vehículo.
Vamos a crear una instancia en tiempo de ejecución del tipo indicado en el primer argumento, para utilizar después sus métodos implementados speedUp & brake.
/** * @param args the command line arguments */ public static void main(String[] args) { String nameClass = args[0]; String nameVehicle = args[1]; try { Class vehicleClass = Class.forName("es.v3.test.reflection." + nameClass); Field[] fields = vehicleClass.getFields(); Method[] methods = vehicleClass.getDeclaredMethods(); try { Vehicle vehicle = (Vehicle) vehicleClass.newInstance(); vehicle.name = nameVehicle; vehicle.speedUp(); vehicle.speedUp(); vehicle.brake(); for (int i=0; i<fields.length; i++) { Field field = fields[i]; System.out.println("Campo '" + field.getName() + "': " + field.get(vehicle)); }//for i for (int i=0; i<methods.length; i++) { Method method = methods[i]; System.out.println("Voy ejecutar el método: '" + method.getName() + "'"); method.invoke(vehicle); }//for i } catch (IllegalArgumentException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } catch (InvocationTargetException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } catch (InstantiationException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } } catch (ClassNotFoundException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } }//main
Con la ayuda del API reflect de Java, hemos instanciado una clase sin conocer a priori el tipo de la misma y únicamente a partir de su nombre, pero veamos más en detalle las funciones utilizadas en el ejemplo.
java.lang.Class forName(String className) javadoc
Devuelve el objeto Class asociado a la clase o interface indicada en el nombre que recibe por parámetro.
java.lang.Object newInstance() javadoc
Crea una nueva instancia del objeto representado por la clase actual.
java.lang.reflect.Field[] getFields() javadoc
Retorna un array con todos los atributos con acceso público de la clase o interfaz representada.
java.lang.reflect.Method[] getMethods() javadoc
Devuelve un array que contiene todos los métodos públicos de la clase o interfaz representada.
java.lang.reflect.Method[] getDeclaredMethods() javadoc
Devuelve un array que contiene todos los métodos declarados en la clase o interfaz representada.
En próximo artículos, veremos cómo realizar este mismo ejemplo de reflexión en otros lenguajes de programación, así como el uso técnicas de reflexión más avanzadas.
¡Te lo agradecemos!