24 septiembre, 2012

Algunos tips con reflexion en Java.

La API de reflexión en Java presenta herramientas que son muy útiles a la hora de hacer que nuestros programas sean "dinámicos" o "expansibles".

Es muy útil en algunos casos, explorar un objeto en tiempo de ejecucion para conocer sus métodos y atributos.

He aqui algunos tips básicos para iniciarse con reflexión.

Clase: Class

La clase Class representa justamente eso: Una clase o una interfaz de Java. Mediante la instancia de Class es posible conocer los métodos que expone la clase, sus atributos, constructores, etc.

Clase: Constructor

La clase Constructor proporciona informacion de y acceso a un constructor de una clase.

Clase: Method

La clase Method proporciona información de y acceso a un método de una clase.

Clase: Field

La clase Field proporciona información de y acceso a un atributo de una clase.


Hay mucho más, pero estos simples conceptos son suficientes para comenzar.

Tomemos como ejemplo, la siguiente clase, la cual nos servirá para explorar un poco de la API java.lang.reflect:

public class Clase {

private String atributo;

public Clase() {
}

public Clase(String atributo) {
    this.atributo = atributo;
}

public String getAtributo() {
    return atributo;
}

public void setAtributo(String atributo) {
    this.atributo = atributo;
}

public void verAtributo() {
    System.out.println("El valor del atributo es: " + atributo);
}

public void metodo() {
    System.out.println("Este es el metodo.");
}

public void metodo(String parametro) {
    System.out.println("Este es el metodo con parametro. El parametro fue: " +       parametro);
}

public static void estatico() {
    System.out.println("Este es el metodo estatico.");
}

public static void estatico(String parametro) {
    System.out.println("Este es el metodo estatico con parametro. El parametro       fue: " + parametro);
}
}


Examinando esta clase, vemos que posee las siguientes características:


  • Un atributo de tipo String.
  • Un constructor sin parámetros.
  • Un constructor que recibe un parámetro de tipo String.
  • Un métod getter para el atributo.
  • Un método setter para el atributo.
  • Un método sin parámetros que imprime un texto en consola.
  • Un método con un parámetro String que imprime un texto en consola.
  • Un método estático sin parámetros que imprime un texto en consola.
  • Un método estático con parámetros que imprime un texto en consola.
Esto es fácil, porque estamos viendo el código fuente de la clase, pero podemos examinarla completamente usando un programa en java.

Lo primero es obtener una instancia de Class.

    Class c = Class.forName("paquete.Clase")
    Class c = Clase.class

Puede ser por el nombre completo de la clase (incluyendo el paquete), o bien invocando directamente el atributo class que toda clase Java tiene.

El siguiente código instancia una clase por su nombre e imprime el nombre de la clase en pantalla:


    Class c = Class.forName("paquete.Clase");
    System.out.println("Clase instanciada: " + c.getName());


La ejecución de este código dará como resultado lo siguente:

    run:
    Clase instanciada: paquete.Clase


Teniendo una instancia de la clase, podemos comenzar a explorarla:

El siguiente código permite obtener los constructores de la Clase:

System.out.println("\nConstructores de la clase:");
Constructor[] cons = c.getConstructors(); //c.getDeclaredConstructors();
for (Constructor co : cons) {
   System.out.println("Constructor : " + co.getName());
   Class[] param = co.getParameterTypes();
   if (param.length == 0) {
      System.out.println("\tConstructor sin parámetros.");
   } else {
      for (Class p : param) {
         System.out.println("\tParametro: " + p.getName());
      }
   }
}


La ejecución del código anterior dará como resultado:


Constructores de la clase:
Constructor : paquete.Clase
     Constructor sin parámetros.
Constructor : paquete.Clase
     Parametro: java.lang.String

Si se nota, ya esposible obtener información de la clase. En este caso el método c.getConstructors() nos permite obtener todos los constructores de la clase. Su hermano c.getDeclaredConstructors() nos permite obtener los constructores que fueron declarados específicamente en la clase que estamos explorando (útil cuando una clase hereda de otra).

Exploremos ahora los atributos que posee la clase:

System.out.println("\nAtributos de la clase:");
Field[] fields = c.getFields();
for (Field f : fields) {
   System.out.println("Atributo: " + f.getName());
   System.out.println("\tTipo del Atributo: " + f.getType().getName());
}



La ejecución del código anterior dará como resultado:


   Atributos de la clase:


En este caso, no hay nada. ¿Por qué? 

Si miramos el código de la clase, veremos que contiene un único atributo de tipo private por lo que no es visible fuera de la clase. Si agregamos el siguiente atributo:


   public String atributoPublico;


Y ejecutamos el código, veremos lo siguiente.


Atributos de la clase:
Atributo: atributoPublico
   Tipo del Atributo: java.lang.String



Ahora exploremos los métodos. El siguiente código muestra los métodos de la clase:


System.out.println("\nMetodos de la clase: ");
Method[] metodos = c.getMethods();
for (Method m : metodos) {
   System.out.println("Metodo: " + m.getName());
   Class[] param = m.getParameterTypes();
   if (param.length == 0) {
      System.out.println("\tEl método no acepta parametros.");
   } else {
      for (Class p : param) {
         System.out.println("\tParametro: " + p.getName());
      }
   }
}


El cual, al ser ejecutado mostrará:


Metodos de la clase:
Metodo: getAtributo
   El método no acepta parametros.
Metodo: setAtributo
   Parametro: java.lang.String
Metodo: verAtributo
   El método no acepta parametros.
Metodo: metodo
   El método no acepta parametros.
Metodo: metodo
   Parametro: java.lang.String
Metodo: estatico
   El método no acepta parametros.
Metodo: estatico
   Parametro: java.lang.String
Metodo: wait
   Parametro: long
Metodo: wait
   Parametro: long
   Parametro: int
Metodo: wait
   El método no acepta parametros.
Metodo: equals
   Parametro: java.lang.Object
Metodo: toString
   El método no acepta parametros.
Metodo: hashCode
   El método no acepta parametros.
Metodo: getClass
   El método no acepta parametros.
Metodo: notify
   El método no acepta parametros.
Metodo: notifyAll
   El método no acepta parametros.


En este caso, vemos métodos que no fueron declarados en la clase. ¿Por que? Porque para obtener los métodos usamos el método getMethods(), el cual retorna todos los métodos en la jerarquía de la clase. Es decir, los métodos de la clase y de su(s) superclase(s). 


Si en lugar de getMethods() usamos getDeclaredMethods(), obtendremos el siguiente resultado:

Metodos de la clase:
Metodo: getAtributo
   El método no acepta parametros.
Metodo: setAtributo
   Parametro: java.lang.String
Metodo: verAtributo
   El método no acepta parametros.
Metodo: metodo
   El método no acepta parametros.
Metodo: metodo
   Parametro: java.lang.String
Metodo: estatico
   El método no acepta parametros.
Metodo: estatico
   Parametro: java.lang.String



Pero ésto sólo nos proporciona información de una clase. Ahora pasaremos a la acción.


Vamos a utilizar 2 instancias de nuestro objeto 'Clase'. En una invocaremos el constructor sin parámetros, y en otra invocaremos el constructor que recibe el parámetro String.

Object o = c.newInstance();
Object o2 = c.getConstructor(String.class).newInstance("Instancia 2");

System.out.println("Instacia o de Clase: " + o);
System.out.println("Instancia o2 de Clase: " + o2);


La ejecución del código anterior dará como resultado:

Instacia o de Clase: paquete.Clase@62f72617
Instancia o2 de Clase: paquete.Clase@4fe5e2c3


En el primer caso, la llamada del método newInstance() llama directamente al contructor sin parámetros. 

Para invocar la llamada al constructor con parámetros, invocamos el método getConstructor() y le entregamos como parámetro String.class. Literalmente le estamos diciendo: "Dame el constructor que recibe como parámetro un String".


Al constructor retornado le invocamos el método newInstance() y le pasamos como parámetro un String.


Ejercicio: Intentar construir la instancia o2, pasándole un Integer (u otro). Tendremos una linda excepción.

Ahora vamos a invocar el método para ver el valor del atributo:

Method verAtributo = c.getDeclaredMethod("verAtributo", null);
verAtributo.invoke(o, null);
verAtributo.invoke(o2, null);



Puede observarse que el código en si es simple. A la clase le pedimos el método llamado verAtributo que no tiene parámetros.


Luego, invocamos el método mediante invoke(), y le pasamos como parámetro la instancia de la clase en la que invocaremos el método y los parámetros del método (si aplica). La ejecución del código anterior, dará como resultado:

El valor del atributo es: null
El valor del atributo es: Instancia 2


Lo cual es correcto, ya que al momento de instanciar, sólo declaramos el atributo en la instancia 2.

Invoquemos un método con parametros.

  Method setAtributo = c.getDeclaredMethod("setAtributo", String.class);
  setAtributo.invoke(o, "Un parámetro");


Como puede notarse, la metodología es similar a la invocación del constructor. Le decimos a la clase "Dame el método llamado setAtributo que recibe un String como parámetro". Luego invocamos el método en la instancia o y le pasamos como parámetro un String.

Si volvemos a invocar el método verAtributo en la instancia o, veremos el siguiente resultado:

El valor del atributo es: Un parámetro

Finalmente, invocaremos un método estático con parámetros.

Method estatico = c.getDeclaredMethod("estatico", String.class);
estatico.invoke(null, "¡Es un método estático!");


Como puede notarse, un método estático no necesita una instancia donde invocarse, por lo que el primer parámetro puede ser NULL. La ejecución del código dará como resultado:

Este es el metodo estatico con parametro. El parametro fue: ¡Es un método estático!

Como puede verse, la API de reflexión es muy potente y trabajando en conjunto con Interfaces y Generics se pueden hacer cosas muy interesantes como por ejemplo una aplicación modular mediante plug-ins o herramientas que analizen y exploren código java... por decir algo.

Hay mucho, mucho más por explorar. Animo al lector a darse un tiempo de explorar esta API ya que soluciona muchos dolores de cabez y permite que nuestras aplicaciones sean flexibles y extensibles con poco esfuerzo.

Archivos: Clase.java Reflexion.java

Salu2!!!

1 comentario:

Unknown dijo...

Justo lo que buscaba, gracias viejo. te debo una