Java: loading dinamico e reflection Scopo del corso integrativo: – Presentare le caratteristiche avanzate di Java che lo rendono un linguaggio molto flessibile pur senza rinunciare ai vantaggi di un typechecking forte ed ad una fase compilativa importante. Premessa Java è un linguaggio ”Data-Driven” come tutti i linguaggi più noti (C, C++, Cobol, ....). Questo significa che non è possibile scrivere un programma che modifica se stesso, attività tipica di una applicazione di AI (ad es il LISP) Gradi di Dinamismo Nonostante la premessa Java permette di fare 2 cose importanti: – Caricamento dinamico di classi che possono modificare il comportamento applicativo (loading dinamico) – Analisi di classi ed oggetti per scoprire la loro struttura pubblica, quali metodi espongono, .... (reflection) Loading Dinamico Permette di realizzare una implementazione pulita del concetto di plugin. Sostanzialmente è possibile scrivere un programma che decide quali classi caricare ed utilizzare rendendo, di conseguenza, il comportamento a runtime completamente dinamico. Capire con un esempio Per capire il concetto precedente facciamo un esempio. Immaginiamo una applicazione che è in grado di eseguire una delle 4 operazioni elementari fra interi (+, *, -, /). Quale delle quattro operazioni eseguire può essere configurata mediante un file. Il Main //Codice non compilante per mancanza gestione eccezioni public class BigBang{ static void main (String[] args) { String oper = ”Somma”; Class c = Class.forName(oper); Operazione op=(Operazione)c.newInstance(); op.qualeOpEsegui(); op.execute(3,5); } L'interfaccia comune public interface Operazione { public void qualeOpEsegui(); public void execute (int a, int b); } La classe Somma public class Somma implements Operazione { public void qualeOpEsegui(){ System.out.println(”io faccio la somma”); } public void execute (int a, int b) { System.out.println (a+”+”+b+”=”+(a+b) ); } } La classe Moltiplicazione public class Moltiplicazione implements Operazione { public void qualeOpEsegui(){ System.out.println(”io faccio la moltiplicazione”); } public void execute (int a, int b) { System.out.println (a+”*”+b+”=”+(a*b) ); } Esecuzione Se eseguimo il precendente main otteniamo in output: io faccio la somma 3+5=8 Esecuzione 2 Se cambiamo il rigo: String operazione = ”somma”; con String operazione = ”moltiplicazione ”; Otterremmo in output: io faccio la moltiplicazione 3*5=15 Cosa succede Il metodo Class.forName permette di caricare dinamicamente una classe spedificandone il nome mediante una stringa. La stringa può essere composta come volete ed ovviamente potrebbe essere presa da un file di configurazione dell'applicazione. Cosa succede Il metodo Class.forName ritorna un oggetto di tipo Class ”cioè una classe”. Questo significa che gli oggetti di tipo Class sono delle ”metaclassi” che possono essere manipolate a run-time. L'oggetto c ottenuto può essere istanziato (newInstance) per ottenere un oggetto della classe che è stata caricata, cioè un oggetto della classe Somma. Cosa succede se cambio LA SOLA STRINGA OPER in moltiplicazione l'applicazione modifica il suo comportamento a run-time in quanto caricherà la classe Moltiplicazione e non la classe Somma ed eseguirà l'operazione corrispondente. Introspezione o Reflection Java permette di analizzare la struttura di una classe dinamicamente. In modo analogo è possibile chiedere l'esecuzione dinamica di un metodo. Reflection Tutte le classi (oltre alla già nota Class) sono contenute nel package java.lang.reflect. Il package dal nome simile (java.lang.ref) non ha nulla a che fare con la reflection (ref = references). Prendere la Classe dell'oggetto E' necessario avere una istanza dell'oggetto Class ( c ). E' ottenibile mediante un Class.forName oppure mediante l'applicazione del metodo getClass() ad un oggetto. Metodi Principali I metodi principali di Class sono: – getName(): restituisce il nome della classe fully qualified – getMethods: restituisce un array di Method che rappresentano tutti i metodi accedibili di quella classe – getMethod(String name, Class[] parameters): restituisce il metodo di nome name e con i tipi di parametri dati in Class – getFields(): che restituisce tutti gli attributi accedibili della classe in un Field[] Method Mediante l'uso della classe Method posso chiedere l'esecuzione di metodi. Es: – Class c = Class.forName(“Somma”); – Somma s = (Operazione) c.newInstance(); – Method m = c.getMethod(“qualeOpEsegui”,null); – m.invoke(s,null); Field Mediante l'uso della classe Field si possono leggere e scrivere gli attributi di una classe (quelli accedibili). – Aereo a = new Aereo(); – Class c = a.getClass(); – Field f = a.getField(“velocita”); //darebbe errore. Perchè? – float v = f.getFloat(a); – v=v+10; – f.setFloat(a,v); Tutti i membri di classe Con i predenti metodi possiamo accedere e modificare i membri accedibili di una classe. E' possibile accedere anche a quelli private con la limitazione di non poter leggere o scrivere valori ma solo guardare la struttura. Questi si ottengono mediante l'uso dei metodi: – c.getDeclaredMethods, – c.getDeclaredFields, – ...