Esempio Tutorial di Java Reflection

Java Reflection fornisce la capacità di ispezionare e modificare il comportamento in esecuzione dell’applicazione. La riflessione in Java è uno degli argomenti avanzati di Java di base. Utilizzando la riflessione di Java possiamo ispezionare una classe, un’interfaccia, un enum, ottenere la loro struttura, i metodi e le informazioni sui campi durante l’esecuzione anche se la classe non è accessibile durante la compilazione. Possiamo anche utilizzare la riflessione per istanziare un oggetto, invocare i suoi metodi, cambiare i valori dei campi.

Java Reflection

  1. Riflessione in Java
  2. Riflessione Java per Classi
  3. Riflessione Java per i campi
  4. Riflessione Java per i metodi
  5. Riflessione Java per i costruttori
  6. Riflessione Java per Annotazioni

  1. Riflessione in Java

La riflessione in Java è un concetto molto potente e ha scarso utilizzo nella programmazione normale, ma è il fondamento per la maggior parte dei framework Java e J2EE. Alcuni dei framework che utilizzano la riflessione Java sono:

  1. JUnit – utilizza la riflessione per analizzare l’annotazione @Test per ottenere i metodi di test e quindi invocarli.
  2. Spring – iniezione delle dipendenze, leggi di più su Iniezione delle Dipendenze Spring
  3. Tomcat contenitore web per instradare la richiesta al modulo corretto analizzando i loro file web.xml e l’URI della richiesta.
  4. Eclipse completamento automatico dei nomi dei metodi
  5. Struts
  6. Hibernate

L’elenco è infinito e tutti utilizzano la riflessione Java perché tutti questi framework non hanno conoscenza e accesso delle classi definite dall’utente, delle interfacce, dei loro metodi, ecc. Non dovremmo utilizzare la riflessione nella programmazione normale dove abbiamo già accesso alle classi e alle interfacce a causa dei seguenti svantaggi.

  • Scarse prestazioni – Poiché la riflessione Java risolve dinamicamente i tipi, comporta elaborazioni come la scansione del classpath per trovare la classe da caricare, causando lentezza delle prestazioni.
  • Restrizioni di sicurezza – La riflessione richiede autorizzazioni di runtime che potrebbero non essere disponibili per il sistema in esecuzione sotto il security manager. Questo può causare il fallimento dell’applicazione a runtime a causa del security manager.
  • Problemi di sicurezza – Utilizzando la riflessione possiamo accedere a parte del codice a cui non siamo autorizzati ad accedere, ad esempio possiamo accedere ai campi privati di una classe e cambiarne il valore. Questo può rappresentare una seria minaccia per la sicurezza e far comportare in modo anomalo l’applicazione.
  • Alta manutenzione – Il codice di riflessione è difficile da capire e debuggare, inoltre eventuali problemi con il codice non possono essere trovati a tempo di compilazione perché le classi potrebbero non essere disponibili, rendendolo meno flessibile e difficile da mantenere.
  1. Riflessione Java per Classi

In Java, ogni oggetto è o un tipo primitivo o un riferimento. Tutte le classi, le enumerazioni, gli array sono tipi di riferimento e ereditano da java.lang.Object. I tipi primitivi sono – booleano, byte, short, int, long, char, float e double. java.lang.Class è il punto di ingresso per tutte le operazioni di riflessione. Per ogni tipo di oggetto, JVM istanzia un’istanza immuabile di java.lang.Class che fornisce metodi per esaminare le proprietà di runtime dell’oggetto e creare nuovi oggetti, invocare i suoi metodi e ottenere/impostare i campi dell’oggetto. In questa sezione, esamineremo i metodi importanti di Class, per comodità, sto creando alcune classi e interfacce con gerarchia di ereditarietà.

package com.journaldev.reflection;

public interface BaseInterface {
	
	public int interfaceInt=0;
	
	void method1();
	
	int method2(String str);
}
package com.journaldev.reflection;

public class BaseClass {

	public int baseInt;
	
	private static void method3(){
		System.out.println("Method3");
	}
	
	public int method4(){
		System.out.println("Method4");
		return 0;
	}
	
	public static int method5(){
		System.out.println("Method5");
		return 0;
	}
	
	void method6(){
		System.out.println("Method6");
	}
	
	// inner public class
	public class BaseClassInnerClass{}
		
	//member public enum
	public enum BaseClassMemberEnum{}
}
package com.journaldev.reflection;

@Deprecated
public class ConcreteClass extends BaseClass implements BaseInterface {

	public int publicInt;
	private String privateString="private string";
	protected boolean protectedBoolean;
	Object defaultObject;
	
	public ConcreteClass(int i){
		this.publicInt=i;
	}

	@Override
	public void method1() {
		System.out.println("Method1 impl.");
	}

	@Override
	public int method2(String str) {
		System.out.println("Method2 impl.");
		return 0;
	}
	
	@Override
	public int method4(){
		System.out.println("Method4 overriden.");
		return 0;
	}
	
	public int method5(int i){
		System.out.println("Method4 overriden.");
		return 0;
	}
	
	// inner classes
	public class ConcreteClassPublicClass{}
	private class ConcreteClassPrivateClass{}
	protected class ConcreteClassProtectedClass{}
	class ConcreteClassDefaultClass{}
	
	//member enum
	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	//member interface
	public interface ConcreteClassPublicInterface{}

}

Guardiamo alcuni dei metodi importanti per la riflessione delle classi.

Ottenere l’oggetto della classe

Possiamo ottenere la classe di un oggetto utilizzando tre metodi: attraverso la variabile statica class, utilizzando il metodo getClass() dell’oggetto e java.lang.Class.forName(String fullyClassifiedClassName). Per i tipi primitivi e gli array, possiamo utilizzare la variabile statica class. Le classi wrapper forniscono un’altra variabile statica TYPE per ottenere la classe.

// Ottenere la classe usando la riflessione
Class concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
	// il metodo sottostante viene utilizzato nella maggior parte dei casi nei framework come JUnit
	//Iniezione di dipendenze Spring, contenitore web Tomcat
	//Auto completamento di nomi di metodi Eclipse, Hibernate, Struts2, ecc.
	//perché ConcreteClass non è disponibile al momento della compilazione
	concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass

//per tipi primitivi, classi wrapper e array
Class booleanClass = boolean.class;
System.out.println(booleanClass.getCanonicalName()); // prints boolean

Class cDouble = Double.TYPE;
System.out.println(cDouble.getCanonicalName()); // prints double

Class cDoubleArray = Class.forName("[D");
System.out.println(cDoubleArray.getCanonicalName()); //prints double[]

Class twoDStringArray = String[][].class;
System.out.println(twoDStringArray.getCanonicalName()); // prints java.lang.String[][]

getCanonicalName() restituisce il nome canonico della classe sottostante. Nota che java.lang.Class utilizza i Generics, che aiutano i framework a garantire che la Classe recuperata sia una sottoclasse della Classe Base del framework. Dai un’occhiata al Tutorial su Java Generics per imparare sui generics e sui suoi wildcard.

Ottieni la Super Classe

Il metodo getSuperclass() su un oggetto Classe restituisce la superclasse della classe. Se questa Classe rappresenta la classe Object, un’interfaccia, un tipo primitivo o void, allora viene restituito null. Se questo oggetto rappresenta una classe array, viene restituito l’oggetto Classe che rappresenta la classe Object.

Class<?> superClass = Class.forName("com.journaldev.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.journaldev.reflection.BaseClass"
System.out.println(Object.class.getSuperclass()); // prints "null"
System.out.println(String[][].class.getSuperclass());// prints "class java.lang.Object"

Ottenere le Classi Membro Pubbliche

Il metodo getClasses() di una rappresentazione di Classe di un oggetto restituisce un array contenente oggetti di Classe che rappresentano tutte le classi pubbliche, interfacce ed enumerazioni che sono membri della classe rappresentata da questo oggetto Classe. Questo include classi pubbliche e membri di interfaccia ereditati dalle superclassi e classi pubbliche e membri di interfaccia dichiarati dalla classe. Questo metodo restituisce un array di lunghezza 0 se questo oggetto Classe non ha classi o interfacce di membri pubblici o se questo oggetto Classe rappresenta un tipo primitivo, una classe di array o void.

Class[] classes = concreteClass.getClasses();
//[classe com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interfaccia com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//classe com.journaldev.reflection.BaseClass$BaseClassInnerClass, 
//classe com.journaldev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));

Ottenere Classi Dichiarate

getDeclaredClasses() metodo restituisce un array di oggetti Class che riflettono tutte le classi e interfacce dichiarate come membri della classe rappresentata da questo oggetto Classe. L’array restituito non include classi dichiarate in classi e interfacce ereditate.

//ottenere tutte le classi, interfacce ed enumerazioni esplicitamente dichiarate in ConcreteClass
Class[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//stampa [classe com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass, 
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum, 
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass, 
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass, 
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//classe com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interfaccia com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

Ottieni la classe dichiarante

getDeclaringClass() il metodo restituisce l’oggetto Class che rappresenta la classe in cui è stata dichiarata.

Class innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//stampa com.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());

Ottenere il nome del pacchetto

getPackage() il metodo restituisce il pacchetto per questa classe. Il caricatore di classi di questa classe viene utilizzato per trovare il pacchetto. Possiamo invocare il metodo getName() di Package per ottenere il nome del pacchetto.


System.out.println(Class.forName("com.journaldev.reflection.BaseInterface").getPackage().getName());

Ottieni Modificatori di Classe

getModifiers() restituisce la rappresentazione int dei modificatori di classe. Possiamo utilizzare il metodo java.lang.reflect.Modifier.toString() per ottenerli nel formato stringa come utilizzato nel codice sorgente.

System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//stampa "public abstract interface"
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers())); 

Ottieni Parametri di Tipo

getTypeParameters() restituisce l’array di TypeVariable se ci sono parametri di tipo associati alla classe. I parametri di tipo sono restituiti nello stesso ordine in cui sono dichiarati.

//Ottieni parametri di tipo (generici)
TypeVariable[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable t : typeParameters)
System.out.print(t.getName()+",");

Ottieni Interfacce Implementate

getGenericInterfaces() restituisce l’array di interfacce implementate dalla classe con informazioni sul tipo generico. Possiamo anche utilizzare getInterfaces() per ottenere la rappresentazione di classe di tutte le interfacce implementate.

Type[] interfaces = Class.forName("java.util.HashMap").getGenericInterfaces();
//stampa "[java.util.Map, interfaccia java.lang.Cloneable, interfaccia java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//stampa "[interfaccia java.util.Map, interfaccia java.lang.Cloneable, interfaccia java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));		

Ottieni tutti i metodi pubblici

getMethods() il metodo restituisce l’array dei metodi pubblici della classe, inclusi i metodi pubblici delle sue superclassi e superinterfacce.

Method[] publicMethods = Class.forName("com.journaldev.reflection.ConcreteClass").getMethods();
//stampa i metodi pubblici di ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));

Ottieni tutti i costruttori pubblici

getConstructors() il metodo restituisce l’elenco dei costruttori pubblici del riferimento di classe dell’oggetto.

//Ottieni tutti i costruttori pubblici
Constructor[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//stampa i costruttori pubblici di ConcreteClass
System.out.println(Arrays.toString(publicConstructors));

Ottieni tutti i campi pubblici

getFields() il metodo restituisce l’array dei campi pubblici della classe, inclusi i campi pubblici delle sue superclassi e superinterfacce.

//Ottieni tutti i campi pubblici
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//stampa i campi pubblici di ConcreteClass, della sua superclasse e delle interfacce superiori
System.out.println(Arrays.toString(publicFields));

Ottieni tutte le annotazioni

getAnnotations() il metodo restituisce tutte le annotazioni per l’elemento, possiamo usarlo con classi, campi e metodi anche. Nota che solo le annotazioni disponibili con la riflessione hanno una politica di conservazione di RUNTIME, consulta Tutorial sulle annotazioni Java. Esamineremo questo in dettaglio nelle sezioni successive.

java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//stampa [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));
  1. Riflessione Java per i campi

La Reflection API fornisce diversi metodi per analizzare i campi della classe e modificarne i valori durante l’esecuzione, in questa sezione esamineremo alcune delle funzioni di riflessione comunemente utilizzate per i metodi.

Ottenere il Campo Pubblico

Nella sezione precedente, abbiamo visto come ottenere l’elenco di tutti i campi pubblici di una classe. L’API di Reflection fornisce anche un metodo per ottenere uno specifico campo pubblico di una classe tramite il metodo getField(). Questo metodo cerca il campo nel riferimento alla classe specificata, quindi nelle interfacce superiori e quindi nelle classi superiori.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");

La chiamata sopra restituirà il campo da BaseInterface che è implementato da ConcreteClass. Se non viene trovato alcun campo, viene lanciata l’eccezione NoSuchFieldException.

Classe Dichiarante il Campo

Possiamo utilizzare il metodo getDeclaringClass() dell’oggetto campo per ottenere la classe che dichiara il campo.

try {
	Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
	Class<?> fieldClass = field.getDeclaringClass();
	System.out.println(fieldClass.getCanonicalName()); //prints com.journaldev.reflection.BaseInterface
} catch (NoSuchFieldException | SecurityException e) {
	e.printStackTrace();
}

Ottenere il Tipo di Campo

Il metodo getType() restituisce l’oggetto Classe per il tipo di campo dichiarato; se il campo è di tipo primitivo, restituisce l’oggetto della classe wrapper.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
Class<?> fieldType = field.getType();
System.out.println(fieldType.getCanonicalName()); //prints int			

Ottieni/Imposta il Valore del Campo Pubblico

Possiamo ottenere e impostare il valore di un campo in un Oggetto utilizzando la riflessione.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
ConcreteClass obj = new ConcreteClass(5);
System.out.println(field.get(obj)); //prints 5
field.setInt(obj, 10); //setting field value to 10 in object
System.out.println(field.get(obj)); //prints 10

Il metodo get() restituisce un Oggetto, quindi se il campo è di tipo primitivo, restituisce la corrispondente Classe Wrapper. Se il campo è statico, possiamo passare l’Oggetto come null nel metodo get(). Ci sono diversi metodi set*() per impostare l’Oggetto sul campo o impostare diversi tipi di tipi primitivi sul campo. Possiamo ottenere il tipo di campo e quindi invocare la funzione corretta per impostare correttamente il valore del campo. Se il campo è finale, i metodi set() lanciano java.lang.IllegalAccessException.

Ottieni/Imposta il Valore del Campo Privato

Sappiamo che i campi e i metodi privati non possono essere accessibili al di fuori della classe, ma utilizzando la riflessione possiamo ottenere/impostare il valore del campo privato disattivando il controllo di accesso java per i modificatori di campo.

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//disabilitare il controllo di accesso con la chiamata del metodo sottostante
privateField.setAccessible(true);
ConcreteClass objTest = new ConcreteClass(1);
System.out.println(privateField.get(objTest)); // prints "private string"
privateField.set(objTest, "private string updated");
System.out.println(privateField.get(objTest)); //prints "private string updated"
  1. Riflessione Java per Metodi

Utilizzando la riflessione possiamo ottenere informazioni su un metodo e possiamo anche invocarlo. In questa sezione, impareremo diversi modi per ottenere un metodo, invocare un metodo e accedere ai metodi privati.

Ottenere un Metodo Pubblico

Possiamo utilizzare il metodo getMethod() per ottenere un metodo pubblico della classe, è necessario passare il nome del metodo e i tipi di parametri del metodo. Se il metodo non viene trovato nella classe, l’API della riflessione cerca il metodo nella superclasse. Nell’esempio seguente, sto ottenendo il metodo put() di HashMap utilizzando la riflessione. L’esempio mostra anche come ottenere i tipi di parametri, i modificatori del metodo e il tipo di ritorno di un metodo.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//ottieni i tipi di parametri del metodo, stampa "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
//ottieni il tipo di ritorno del metodo, restituisce "class java.lang.Object", riferimento di classe per void
System.out.println(method.getReturnType());
//ottieni i modificatori del metodo
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"

Invocare il metodo pubblico

Possiamo utilizzare il metodo invoke() dell’oggetto Method per invocare un metodo. Nell’esempio di codice sottostante, sto invocando il metodo put su HashMap usando la riflessione.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
Map<String, String> hm = new HashMap<>();
method.invoke(hm, "key", "value");
System.out.println(hm); // prints {key=value}

Se il metodo è statico, possiamo passare NULL come argomento dell’oggetto.

Invocare metodi privati

Possiamo utilizzare getDeclaredMethod() per ottenere il metodo privato e poi disabilitare il controllo degli accessi per invocarlo. Nell’esempio sottostante viene mostrato come possiamo invocare il metodo3() di BaseClass che è statico e non ha parametri.

// invocazione del metodo privato
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
  1. Riflessione Java per i costruttori

La Reflection API fornisce metodi per ottenere i costruttori di una classe da analizzare e possiamo creare nuove istanze della classe invocando il costruttore. Abbiamo già imparato come ottenere tutti i costruttori pubblici.

Ottieni il costruttore pubblico

Possiamo utilizzare il metodo getConstructor() sulla rappresentazione della classe dell’oggetto per ottenere un costruttore pubblico specifico. Nell’esempio seguente viene mostrato come ottenere il costruttore di ConcreteClass definito sopra e il costruttore senza argomenti di HashMap. Mostra anche come ottenere l’array dei tipi di parametro per il costruttore.

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//ottenimento dei parametri del costruttore
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
		
Constructor hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"

Istanza dell’oggetto utilizzando il costruttore

Possiamo utilizzare il metodo newInstance() sull’oggetto costruttore per istanziare una nuova istanza della classe. Poiché utilizziamo la reflection quando non abbiamo le informazioni sulle classi al momento della compilazione, possiamo assegnarlo a Object e poi utilizzare ulteriormente la reflection per accedere ai suoi campi e invocare i suoi metodi.

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//ottenimento dei parametri del costruttore
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
		
Object myObj = constructor.newInstance(10);
Method myObjMethod = myObj.getClass().getMethod("method1", null);
myObjMethod.invoke(myObj, null); //prints "Method1 impl."

Constructor hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
HashMap myMap = (HashMap) hashMapConstructor.newInstance(null);
  1. Riflessione per le Annotazioni

Le annotazioni sono state introdotte in Java 1.5 per fornire informazioni di metadati sulla classe, sui metodi o sui campi e ora sono ampiamente utilizzate in framework come Spring e Hibernate. L’API di riflessione è stata estesa anche per fornire supporto nell’analisi delle annotazioni durante l’esecuzione. Utilizzando l’API di riflessione, possiamo analizzare le annotazioni il cui criterio di conservazione è Runtime. Ho già scritto un tutorial dettagliato sulle annotazioni e su come possiamo utilizzare l’API di riflessione per analizzarle, quindi ti consiglierei di dare un’occhiata a Tutorial sulle Annotazioni Java. Questo è tutto per il tutorial sull’esempio di riflessione in Java, spero che ti sia piaciuto e che tu abbia compreso l’importanza dell’API di riflessione in Java.

Source:
https://www.digitalocean.com/community/tutorials/java-reflection-example-tutorial