Download fundamentos de informática

Document related concepts
no text concepts found
Transcript
Técnicas avanzadas de programación
Introspección
Ramiro Lago
Introducción



Java tiene interesantes mecanismos para cargar clases
de manera dinámica, conocer sus métodos y atributos,
etc. Son formas de obtener información interna de las
clases, incluso los objetos Java pueden informarnos de
su propia estructura. Por ello se llaman utilidades de
introspección.
La clase más importante es java.lang.Class. Es una
clase que nos permite describir cualquier clase Java.
Dicho de otra forma, es un descriptor o referencia de
una clase.
Un paquete relevante es java.lang.reflect
2
Conocer el tipo

En tiempo de ejecución puede saber el tipo (clase) que
manejamos:

Si tenemos un objeto, puedo conocer su clase:
Coche miCoche = new Coche();
Class clase = miCoche.getClass();
System.out.println("Clase:" +clase.getName() );
Lo que hemos hecho es obtener un descriptor de clase (tipo
Class) mediante miCoche.getClass(). Este descriptor nos devuelve
su nombre mediante getName()

Si tenemos una clase (no hay instancias):
Coche.class.getName()

getName() nos devuelve el nombre de la clase, incluyendo la
jerarquía de paquetes. En nuestro ejemplo:
newInstance.dominio.Coche
3
Carga dinámica


Podemos cargar de manera dinámica un objeto, es decir, determinar en tiempo de ejecución (y no en
tiempo de programación) la clase que vamos a instanciar
Primero haremos una pequeña clase (Coche) de prueba:
package newInstance.dominio;
public class Coche extends Vehiculo {
private int plazas = 5;
public
public
public
public
public
}

}
Coche() {}
Coche( int plazas )
int getPlazas()
void setPlazas( int p )
String toString() {
try {
catch (Exception e) {
{ this.plazas = plazas; }
{ return plazas; }
{ plazas = p; }
return super.toString() + " Plazas:" + String.valueOf(plazas); }
return "-1"; }
A continuación crearemos un objeto del tipo Class, que es un descriptor de la estructura de datos
o clase. Lo conseguimos con forName(). El paso siguiente es crear una instancia de la clase con
newInstance().
Class clase = Class.forName( “newInstance.dominio.Coche” );
Object objeto = clase.newInstance();
System.out.println(“Coche:" + objeto.toString() );


Necesitamos al menos un constructor sin parámetros (Coche()) para el uso de newInstance() o bien
no especificar ninguno. Pero, si se especifica uno que tenga parámetros (Coche( int plazas )),
entonces debe también implementar uno sin parámetros, ya que este es el que usa
newInstance()
Hemos determinado la clase desde una cadena en el código (newInstance.dominio.Coche). Esto no es
muy dinámico. Pero las posibilidades de carga dinámica son evidentes:


Un ejemplo puede ser que en función de una acción del usuario podemos instanciar una clase u otra. En un
archivo properties podemos tener la asociación de acciones y clases.
Otro ejemplo: puedo cargar todas las clases que haya en un determinado directorio, sin tener que determinar
estáticamente (a priori en el código) las clase que cargo.
4
Carga dinámica. Ejemplo (I)

Vamos a instanciar todas las clases de un directorio (paquete). strDirClases se obtiene previamente
desde un archivo properties:
….
//// Obtengo directorio de clases
File fDirClases = new File( strDirClases );
if ( !fDirClases.isDirectory()) {
System.out.println("El directorio de clases no existe" );
return;
}
//// Obtengo clases del directorio
File[] listaFicheros = fDirClases.listFiles( new ClassFileFilter() ); // Ver siguiente transparencia
if (listaFicheros.length == 0) {
System.out.println("No hay clases en el directorio de clases" );
return;
}
//// Instancio clases
System.out.println("\nClases instanciadas:" );
String strClase = "";
Object objeto = null;
for (int i = 0; i < listaFicheros.length; i++) {
// Quito el sufijo .class
strClase = listaFicheros[i].getName().substring(0, listaFicheros[i].getName().indexOf("."));
//// lec es un lector de parámetros de archivo properties (en este ejemplo el paquete es “newInstance.dominio.”)
Class clase = Class.forName( lec.getParametro("paquete") + strClase );
objeto = clase.newInstance();
System.out.println("Clase:" + strClase + " Objeto:" + objeto.toString() );
}
….

Resultado:
Clases instanciadas:
Clase:Coche Objeto:Id:1 Plazas:5
Clase:Vehiculo Objeto:Id:2
5
Carga dinámica. Ejemplo (II)

En este ejmplo se obtienen los archivos .class del
directorio fDirClases (tipo File) por medio de la llamada:
File[] listaFicheros = fDirClases.listFiles( new ClassFileFilter() );

listFiles usa un filtro de ficheros para obtener sólo los
.class. El filtro es una clase que implementa el interfaz
java.io.FileFilter, lo que le obliga a implementar el método
accept(). Este método devuelve true si el archivo
(parámetro) pasa el filtro:
public class ClassFileFilter implements java.io.FileFilter {
public boolean accept(java.io.File f) {
if (!f.isDirectory()) {
String name = f.getName().toLowerCase();
return name.endsWith("class");
}
return false;
}
}
6
Manejo de la estructura de la clase (I)


Podemos obtener la clase madre, los atributos, constructores y métodos de
una clase. Para ello usaremos java.lang.reflect.
Para hallar la clase madre:
Class clase = Class.forName( "newInstance.dominio.Coche");
Class claseMadre = clase.getSuperclass();
System.out.println( "Clase " + clase.getName() + ", hereda de " +claseMadre.getName());

Para atributos, métodos y constructores es muy sencillo: los atributos se
representan por la clase Field, los constructores por Constructor y Method
para los métodos. Partimos del descriptor de clase:
Class clase = Class.forName( "newInstance.dominio.Coche");

El descriptor de clase tiene los métodos getFields(), getConstructors() y
getMethod(), que nos devuelven arrays de los objetos que buscamos. Pero
sólo nos devuelve elementos públicos, hay una versión “Declared” de cada
método get que nos devuelve cualquier elemento (privado, protegido o
público), por ejemplo: getDeclaredFields().
Field atrib[] = clase.getDeclaredFields(); // getFields() nos da solo los public
printLista( "Atributos:", atrib );
//// Constructores y métodos
Constructor cons[] = clase.getConstructors(); // Sólo public
printLista( "Constructores:", cons );
….
public static void printLista( String titulo, Object obj[]) {
System.out.println( titulo );
for ( int i = 0; i < obj.length; i++)
System.out.println( " " + obj[i].toString());
}
7
Manejo de la estructura de la clase (II)

Además podemos invocar un método. Primero debemos encontrarlo,
para lo cual usamos el método getMethod(), especificando el nombre
del método y los tipos de sus parámetros. En este caso buscamos el
método “setPlazas”, que tiene un parámetro int:
Class tiposParam[] = { int.class };
Method metSetPlazas = clase.getMethod( "setPlazas", tiposParam );

A continuación lo invocamos. También necesitamos un array, pero esta
vez para los valores de los parámetros:
Coche miCoche = new Coche();
Object argumentos[] = {7};
metSetPlazas.invoke( miCoche, argumentos );


Invoke: si el método es static el primer argumento (miCoche en nuestro
ejemplo) puede ser null. El segundo puede ser null si no tiene
parámetros, en concreto (Object[]) null.
Para comprobar el resultado puede usar miCoche.toString()
8
Midiendo el tiempo

Hay una forma sencilla de medir el tiempo de invocación de
un método, con System.currentTimeMillis():
//// Busca método toString (de otra forma, recorriendo array de Method) y lo invoca
//// Ojo: búsqueda imperfecta, en caso de sobrecarga de métodos, toma el primero
//// Además con System.currentTimeMillis() medimos tiempo de llamada
for ( int i = 0; i < mets.length; i++) {
if ( mets[i].getName().equals( "toString") ) {
System.out.println( "Encontrado método " + mets[i].getName());
long inicio = System.currentTimeMillis();
String cadena = (String) mets[i].invoke( miCoche, (Object[]) null);
long fin = System.currentTimeMillis();
System.out.println( "Invocado método: " + cadena);
System.out.println( "Tiempo empleado por invoke(): " + (fin - inicio));
}
}
9