About.me

martes, 24 de octubre de 2006

Reflexión en Java

El presente artículo es una introducción a la reflexión en Java. Esta técnica permite "destripar" las clases de Java, obteniendo la información de su estructura a bajo nivel. ¿Qué ventajas tiene ésto?. La verdad es que muchas, pero os voy a ilustrar con un ejemplo.

Imaginemos el desarrollo de un gateway que está basado en servicios. Cada servicio es atendido por una clase especializada, dependiendo de un tipo de mensaje.

Si queremos ejecutar un servicio específico que atienda una solicitud determinada, lo normal es hacer un anidamiento de sentencias IF para que instancie y ejecute cada clase según el caso. Esto tiende a complicar el desarrollo, hace ilegible el código, no es óptimo, y para integrar este gateway en otros proyectos, habrá que borrar IF's y crear otros para las clases de servicio que correspondan.

Pero si utilizamos la reflexión, este anidamiento de IF's desaparece, dejando lugar a una única instanciación dinámica, que puede ser recogida de una base de datos o de un fichero. ¿Suena bien, verdad?

Para simplificar este caso, se ha realizado una simulación con clases básicas.

Lo primero es definir una interfaz, la cual servirá para definir la estructura de las clases que harán de servicio, creando los métodos por los cuales se invocan.


public interface IInterface {
public int operacion(int a, int b);
}



Una vez definida la interfaz, se crean las clases servicio basadas en dicha interfaz:

Clase A


public class A implements IInterface{
private int s1, s2;

public A() {
s1 = 0;
s2 = 0;
}

public A(int a, int b) {
s1 = a;
s2 = b;
}

public int operacion(int a, int b) {
return a+b;
}

}



Clase B


public class B implements IInterface{

public int operacion (int a, int b) {
return a-b;
}

}



Para obtener un objeto a partir de una clase, de la cual no se conoce el nombre, se utiliza el siguiente código:


String clase = "A";

Class c = Class.forName(clase);


A partir de ahora, el objeto "c" contendrá la información referida a dicho objeto. A partir de aquí se podrá saber si es una clase, una interfaz, si es pública, privada, protegida, sincronizada, abstracta, etc. Se podrá acceder a todos sus constructores, métodos, parámetros, etc. (mirar código de ejemplo más abajo).

Lo realmente importante para nuestro ejemplo, es poder instanciar dicha clase y ejecutar sus métodos. Para ello se crea la instancia mediante la interfaz, que define el tipo de clase, y a través del objeto obtenido anteriormente:


String clase = "A";

Class c = Class.forName(clase);

// Instanciacion dinamica
IInterface objeto = (IInterface) c.newInstance();
System.out.println("Resultado: " + objeto.operacion(3, 2));


Como se puede apreciar, ya no estamos limitados por los nombres de las clases escritos estáticamente en el código, si no que podemos obtenerlos de un fichero o de una tabla de base de datos, e ir instanciando dinámicamente.

En el siguiente ejemplo, se ilustra cómo obtener algunas propiedades de las clases que deseamos instanciar.


import java.lang.reflect.*;

public class MainClass {
public MainClass() {
}

public static void main(String[] args) {
MainClass mainclass = new MainClass();
String clase="B";
String aux = "";
try {
Class c = Class.forName(clase);
Class i[] = c.getInterfaces();

System.out.println("Nombre de la clase: " + c.getName());
int m = c.getModifiers();
System.out.println("Modificadores: " + m);

aux = "\n - ATRIBUTOS -\n";

// Obtencion de atributos de clase o interfaz
aux += "\nInterface: ";

if (Modifier.isInterface(m))
aux += "SI";
else {
aux += "NO";

aux += "\nClase estatica: ";

if (Modifier.isStatic(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase final: ";

if (Modifier.isFinal(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase anonima: ";

if (c.isAnonymousClass())
aux += "SI";
else
aux += "NO";

aux += "\nClase publica: ";

if (Modifier.isPublic(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase privada: ";

if (Modifier.isPrivate(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase protegida: ";

if (Modifier.isProtected(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase abstracta: ";

if (Modifier.isAbstract(m))
aux += "SI";
else
aux += "NO";

aux += "\nClase sincronizada: ";

if (Modifier.isSynchronized(m))
aux += "SI";
else
aux += "NO";

aux += "\nInterfaces implementadas: ";

for (int n =0; n<i.length; n++)
aux+="\n " + i[n].getName();
}

System.out.println(aux);

// Obtencion de propiedades (variables publicas)
aux = "\n- PROPIEDADES -\n";
Field[] f = c.getDeclaredFields();

for (int n=0; n<f.length; n++) {
aux+= "\nNombre: " + f[n].getName();
aux+= " - Tipo: " + f[n].getType().getName();
}

System.out.println(aux);

// Obtencion de constructores
aux = "\n- CONSTRUCTORES -\n";
Constructor[] co = c.getDeclaredConstructors();

for (int n=0; n<co.length; n++) {
aux+= "\nNombre: " + co[n].getName();
Class pa[] = co[n].getParameterTypes();
for (int y=0; y<pa.length; y++)
aux += " - Parametro: " + pa[y].getName();
}

System.out.println(aux);


// Instanciacion dinamica
IInterface objeto = (IInterface) c.newInstance();
System.out.println("Resultado: " + objeto.operacion(3, 2));
}
catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
}


Todo esto se aplica a clases ya compiladas, por lo que es posible realizar una ingeniería inversa a partir de un simple jar.

Espero que os haya gustado el artículo y que os sea muy útil en el futuro.

Enlace de interés: Trail: The reflection API

11 comentarios:

Anónimo dijo...

Genial :)
Muchas gracias, me ha venido al pelo el artículo.

SALUD

Oscar (TxOs) dijo...

Te pasaste!!! tu ejemplo es 100 puntos..muy pero muy bueno.
Gracias.

Bikialf dijo...

La Reflexión parece el climax de todo lo orientado a objetos. El ejemplo es realmente representativo, muchísimas gracias por la aportación.
Seguro que tiene infinidad de aplicaciones más, si pudieras indicar donde encontrar alguna más, sería perfecto.
De todas formas, el artículo es genial

Jeandy Bryan dijo...

Sin duda genial el artículo, mi duda es:
Si mi aplicación no tuviera todas las clases necesarias dentro, o sea si nacen nuevos protocolos surgiría la necesidad de implementar nuevas clases para comprender estos protocolos, como mediante este método podría cargar nuevas clases, os sea clases que pudieran estar fuera de mi aplicación.

Yanzely Ng dijo...

Estuvo excelente el ejemplo. Gracias por su artículo, me dejo mucho más claro el tema.

ocars dijo...

Gracias por la info, me fue muy util.

Anónimo dijo...

Una explicacion muy sencilla para este tema Muchas gracias

PaK0s dijo...

Excelente Post =D.. me ayudo mucho a comprender reflexión, ya andaba buscando hace rato esto pero solo encontraba documentación en ingles.. y me desespere y termine usando IF's.. =P... no encostraba la forma de ejecutar métodos

Andres dijo...

Un ejemplo muy ilustrativo, concreto y útil.
Solo le haría un pequeño cambio porque de lo contrario no funciona, o sea, lanza una excepción al no encontrar la clase B
La clase debe ser llamada incluyendo el full path del paquete, por lo que quedaría:
String clase = "com.mi.paquete.B"

Saludos,
Andrés.

Anónimo dijo...

Aver si me ayudas un poquito aver explico tengo una clase:

public class grid_marca {

public static DefaultTableModel cont_table;

public static JPanel grid_marca(){

//aqui el cuerpo de la tabla


}



}

entonces yo instancio desde otra clase:

public class Grid {

public void grid(){

grid_marca objeto=new grid_marca()
objeto.cont_table.addRow(fila);


}

}

quiero hacer dinamico el "grid_marca" (osea puede ser grid_venta,etc) y agregar los registro desde este clase.

De antemano le agredeceria mucho su respuesta.

Anónimo dijo...

Amigos tengo una pregunta mm aver si me ayudan un poco tengo una clase A donde tengo una tabla , y luego tengo de una clase B de esta clase B yo quiero instanciar dinamicamente de esta forma

grid_marca objeto=new grid_marca()
objeto.cont_table.addRow(fila);

lo que quiero es que el “grid_marca” sea dinamico(por ejemplo quiero poner grid_venta u otro) y puedo agregar los datos a la tabla de esta segunda clase.

Le agradeciria mucho su respuesta.