jueves, julio 22, 2010

Reflexión en el desarrollo de Software

Introducción

De acuerdo con la Real Academia de la Lengua Española, reflexión(1) es la "Acción y efecto de reflexionar", en tanto que reflexionar(2) es: "Considerar nueva o detenidamente algo".

Wikipedia menciona la reflexión en informática(3) como "la capacidad que tiene un programa de ordenador  para observar y opcionalmente modificar su estructura de alto nivel".
 
Definición

En mis propios términos:

La reflexión en la programación de Software es la característica de un lenguaje  que permite conocer su estructura de manera dinámica (en tiempo de ejecución).

Ejemplo:

A continuación se muestra un ejemplo que tiene la siguiente funcionalidad:

  1. Crear una clase del tipo que se proporcione en el primer parámetro.
  2. Crear un objeto a partir de la clase creada en el punto anterior.
  3. Obtener el valor de la propiedad indicada en el segundo parámetro.
  4. Modificar el valor de la propiedad que se obtuvo en el punto anterior con el valor del tercer parámetro.

Versión Java:

Antes de efectuar el ejemplo es necesario:
  1. Tener la JDK versión >= 1.5 instalada (4)
  2. Saber compilar y ejecutar clases Java (5).

El ejemplo EjemploReflexion funciona con cualquier clase. La única restricción (por sencillez en la programación) es que la propiedad a modificar sea de tipo java.lang.String.

Para simplificar aún más la prueba se proporciona también la clase Cliente.

Para ejecutar el ejemplo solo es necesario compilar ambas clases y ejecutarlas mediante el comando:

java EjemploReflexion org.josuemb.Cliente nombre Josue


En caso de que se desee ejecutar el ejemplo con otra clase, la sintaxis sería:

java EjemploReflexion NombreClase NombrePropiedad Valor

Archivo EjemploReflexion.java:

package org.josuemb;

import java.lang.reflect.Field;

/**
 * 
 * Clase que muestra un ejemplo de reflexión.
 * 
 * @author josuemb
 * 
 */
public class EjemploReflexion {

 /**
  * @param args
  */
 @SuppressWarnings("unchecked") //Evita un warning al compilar
 public static void main(String[] args) {
  try {
   if (args.length < 3) {
    System.out.println("Error en llamado\n");
    System.out.println("Sintaxis:");
    System.out
      .println("\tjava EjemploReflexion NombreClase NombrePropiedad Valor");
    System.out.println("Ejemplo:");
    System.out
      .println("\tjava EjemploReflexion org.josuemb.Cliente nombre Josue");
    System.exit(1);
   }
   System.out.println("Creando clase [" + args[0]
     + "] de manera dinamica...");
   // Crea la clase proporcionada en el primer parámetro de manera
   // dinámica.
   Class clase = Class.forName(args[0]);
   System.out.println("Creando objeto de tipo [" + clase.getName()
     + "] manera dinámica...");
   //Crea un nuevo objeto de manera dinamica
   Object objeto = clase.newInstance();
   System.out.println("Obteniendo propiedad [" + args[1]
     + "] del nuevo objeto de manera dinamica...");
   // Obtiene la propiedad proporcionada en el segundo parámetro de
   // manera dinámica.
   Field propiedad = objeto.getClass().getDeclaredField(args[1]);
   // Es necesario establecer esta propiedad para acceder a los campos
   // de la clase directamente.
   propiedad.setAccessible(true);
   // Obtiene el valor de la propiedad
   Object valor1 = propiedad.get(objeto);
   // Imprime el valor de la propiedad
   System.out.println(propiedad.getName() + "=" + valor1);
   System.out.println("Modificando valor de propiedad ["
     + propiedad.getName() + "]...");
   //Establece el nuevo valor a la propiedad
   propiedad.set(objeto, args[2]);
   // Obtiene el valor de la propiedad
   Object valor2 = propiedad.get(objeto);
   // Imprime el valor de la propiedad
   System.out.println(propiedad.getName() + "=" + valor2);
   System.exit(0);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

}

Archivo Cliente.java:


package org.josuemb;

public class Cliente {
 private String nombre;
 private String lugar;

 public String getNombre() {
  return nombre;
 }

 public void setNombre(String nombre) {
  this.nombre = nombre;
 }

 public String getLugar() {
  return lugar;
 }

 public void setLugar(String lugar) {
  this.lugar = lugar;
 }
}  

El resultado de la ejecución es el siguiente:

josuemb@josuemb-laptop:~/workspaces/Ejemplos/EjemploReflexion/bin$ java org.josuemb.EjemploReflexion org.josuemb.Cliente nombre Josue
Creando clase [org.josuemb.Cliente] de manera dinamica...
Creando objeto de tipo [org.josuemb.Cliente] manera dinámica...
Obteniendo propiedad [nombre] del nuevo objeto de manera dinamica...
nombre=null
Modificando valor de propiedad [nombre]...
nombre=Josue

Versión Groovy:

Antes de efectuar el ejemplo es necesario:
  1. Tener la Groovy versión >= 1.7 instalado (6)
  2. Saber compilar clases Groovy (7).
  3. Saber ejecutar scripts Groovy (8).

El ejemplo funciona con cualquier clase. La única restricción (por sencillez en la programación) es que la propiedad a modificar sea de tipo java.lang.String.

Para simplificar aún más la prueba se proporciona también la clase Cliente.

Para ejecutar el ejemplo solo es necesario compilar la clase Cliente mediante el comando:

groovyc EjemploReflexion.groovy


Y después ejecutar el Script EjemploReflexion mediante el comando:

groovy EjemploReflexion.groovy org.josuemb.Cliente nombre Josue

En caso de que se desee ejecutar el ejemplo con otra clase, la sintaxis sería:

groovy EjemploReflexion.groovy nombreClase nombrePropiedad Valor

Archivo EjemploReflexion.groovy:

if(args.size() < 3 ){
    println """Error en llamado
Sintaxis:
    groovy EjemploReflexion.groovy NombreClase NombrePropiedad Valor
Ejemplo:
    groovy EjemploReflexion.groovy org.josuemb.Cliente nombre Josue"""
    System.exit 1
}

println "Creando clase [${args[0]}] de manera dinamica..."
// Crea la clase proporcionada en el primer parámetro de manera dinámica.
def clase = args[0] as Class;

println "Creando objeto de tipo [$clase.name] de manera dinamica..."
//Crea un nuevo objeto de manera dinamica
def objeto = clase.newInstance()

println "Obteniendo propiedad [${args[1]}] del nuevo objeto de manera dinamica..."

println "${args[1]}=${objeto[args[1]]}"

//Establece el nuevo valor a la propiedad
objeto[args[1]] = args[2]

println "Modificando valor de propiedad [${args[1]}]..."

objeto[args[1]] = args[2]

println "${args[1]}=${objeto[args[1]]}"
 
Archivo Cliente.groovy:

package org.josuemb

class Cliente{
    String nombre
    String lugar
}

El resultado de la ejecución es el siguiente:

josuemb@josuemb-laptop:~$ groovy EjemploReflexion.groovy org.josuemb.Cliente nombre Josue
Creando clase [org.josuemb.Cliente] de manera dinamica...
Creando objeto de tipo [org.josuemb.Cliente] de manera dinamica...
Obteniendo propiedad [nombre] del nuevo objeto de manera dinamica...
nombre=null
Modificando valor de propiedad [nombre]...
nombre=Josue


Conclusión

La reflexión nos permite hacer cosas bastante interesantes como por ejemplo: conocer la estructura interna de una clase, sus propiedades, constructores, métodos (parámetros de los mismos), ejecutar los métodos, incluso, ¡obtener y modificar información de miembros privados de una clase! y todo esto aún si no tenemos ni el código ni la documentación de una clase.
 
Referencias
  1. Reflexión: http://buscon.rae.es/draeI/SrvltConsulta?TIPO_BUS=3&LEMA=reflexion
  2. Reflexionar: http://buscon.rae.es/draeI/SrvltConsulta?TIPO_BUS=3&LEMA=reflexionar
  3. Reflexión Informática: http://es.wikipedia.org/wiki/Reflexi%C3%B3n_%28inform%C3%A1tica%29
  4. Instalación de la JDK 1.6: http://java.sun.com/javase/6/webnotes/install/index.html
  5.  Compilar y ejecutar clases Java: http://download.oracle.com/docs/cd/E17409_01/javase/tutorial/getStarted/cupojava/index.html
  6. Instalación de Groovy 1.7: http://groovy.codehaus.org/Tutorial+1+-+Getting+started
  7. Compilar clases Groovy: http://docs.codehaus.org/display/GROOVY/Compiling+Groovy
  8. Ejecutar scripts Groovy: http://groovy.codehaus.org/Running
  9. Artículo "Usando Java Reflection" (Oracle): http://java.sun.com/developer/technicalArticles/ALT/Reflection/  
  10. Artículo "Programación dinámica en Java, parte 2" (IBM): http://www.ibm.com/developerworks/library/j-dyn0603/

3 comentarios:

Alberto Vilches dijo...

Es interesante ver como Groovy, al ser un lenguaje dinámico, no requiere casi el uso del api reflection para acceder a los atributos de cualquier clase: se usa la misma sintaxis que para acceder a los objetos como si fueran mapas. :)
Un saludo

josuemb dijo...

Uno de los objetivos de utilizar también Groovy en el ejemplo es contrastar código Java vs Groovy y creo que el resultado es bastante afortunado.

Saludos.

Javier F.A. dijo...

Al tener una API que permite Reflection, ¿se podría considerar que Java no es un lenguaje rigurosamente estático? Es decir, en esencia es estático ya que, en tiempo de compilación no se permite, entre otras cosas, tipado dinámico. Sin embargo, mediante el uso de la API Reflection, determinadas excepciones que deberían de lanzarse en tiempo de compilación saltarán en tiempo de ejecución (por ejemplo la ejecución de un método que no existe) tal y como sucede en los lenguajes dinámicos.

La respuesta, seguramente, se halla en si las APIs se deben de considerar parte del "lenguaje base" o no. Si se excluyen, obviamente, Java se considerará un lenguaje puramente estático.

Un cordial saludo