Download fundamentos de informática

Document related concepts
no text concepts found
Transcript
Técnicas avanzadas de programación
Introspección
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 Coche() {}
public Coche( int plazas )
public int getPlazas()
public void setPlazas( int p )
public 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