Ejemplo de tutorial de Reflexión en Java

La reflexión en Java proporciona la capacidad de inspeccionar y modificar el comportamiento en tiempo de ejecución de la aplicación. La reflexión en Java es uno de los temas avanzados de Java principal. Utilizando la reflexión en Java podemos inspeccionar una clase, interfaz, enumeración, obtener su estructura, métodos e información de campos en tiempo de ejecución incluso si la clase no es accesible en tiempo de compilación. También podemos usar la reflexión para instanciar un objeto, invocar sus métodos, cambiar valores de campo.

La reflexión en Java

  1. Reflexión en Java
  2. Reflexión de Java para Clases
  3. Reflexión en Java para Campos
  4. Reflexión en Java para Métodos
  5. Reflexión en Java para Constructores
  6. Reflexión en Java para Anotaciones

  1. Reflexión en Java

La reflexión en Java es un concepto muy poderoso y tiene poco uso en la programación normal, pero es la columna vertebral de la mayoría de los marcos de Java y J2EE. Algunos de los marcos que utilizan la reflexión en Java son:

  1. JUnit – utiliza la reflexión para analizar la anotación @Test para obtener los métodos de prueba y luego invocarlos.
  2. Spring – inyección de dependencias, leer más en Inyección de Dependencias en Spring
  3. Tomcat contenedor web para enviar la solicitud al módulo correcto analizando sus archivos web.xml y URI de solicitud.
  4. Eclipse autocompletado de nombres de métodos
  5. Struts
  6. Hibernate

La lista es interminable y todos utilizan reflexión en Java porque ninguno de estos frameworks tiene conocimiento ni acceso a clases definidas por el usuario, interfaces, sus métodos, etc. No deberíamos utilizar reflexión en programación normal donde ya tenemos acceso a las clases e interfaces debido a los siguientes inconvenientes.

  • Rendimiento Pobre – Dado que la reflexión en Java resuelve los tipos dinámicamente, implica procesos como escanear el classpath para encontrar la clase a cargar, lo que ralentiza el rendimiento.
  • Restricciones de Seguridad – La reflexión requiere permisos en tiempo de ejecución que podrían no estar disponibles para el sistema que se ejecuta bajo un administrador de seguridad. Esto puede hacer que su aplicación falle en tiempo de ejecución debido al administrador de seguridad.
  • Problemas de Seguridad – Al utilizar reflexión, podemos acceder a partes del código a las que no se supone que debemos acceder, por ejemplo, podemos acceder a campos privados de una clase y cambiar su valor. Esto puede representar una seria amenaza de seguridad y hacer que su aplicación se comporte de manera anormal.
  • Mantenimiento Elevado – El código de reflexión es difícil de entender y depurar, además, cualquier problema con el código no se puede encontrar en tiempo de compilación porque las clases podrían no estar disponibles, lo que lo hace menos flexible y difícil de mantener.
  1. Reflexión de Java para Clases

En Java, cada objeto es o un tipo primitivo o una referencia. Todos las clases, enumeraciones y arreglos son tipos de referencia y heredan de java.lang.Object. Los tipos primitivos son – boolean, byte, short, int, long, char, float y double. java.lang.Class es el punto de entrada para todas las operaciones de reflexión. Para cada tipo de objeto, JVM instancia una instancia inmutable de java.lang.Class que proporciona métodos para examinar las propiedades en tiempo de ejecución del objeto y crear nuevos objetos, invocar sus métodos y obtener/establecer campos de objetos. En esta sección, examinaremos métodos importantes de la clase, para mayor conveniencia, estoy creando algunas clases e interfaces con jerarquía de herencia.

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");
	}
	
	// Clase interna pública
	public class BaseClassInnerClass{}
		
	//miembro público 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;
	}
	
	// clases internas
	public class ConcreteClassPublicClass{}
	private class ConcreteClassPrivateClass{}
	protected class ConcreteClassProtectedClass{}
	class ConcreteClassDefaultClass{}
	
	// enum miembro
	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	// interfaz de miembro
	public interface ConcreteClassPublicInterface{}

}

Veamos algunos de los métodos importantes de reflexión para clases.

Obtener Objeto de Clase

Podemos obtener la Clase de un objeto usando tres métodos: a través de la variable estática class, usando el método getClass() del objeto y java.lang.Class.forName(String fullyClassifiedClassName). Para tipos primitivos y matrices, podemos usar la variable estática class. Las clases envoltorio proporcionan otra variable estática TYPE para obtener la clase.

// Obtener Clase usando reflexión
Class concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
	// el siguiente método se utiliza la mayoría de las veces en marcos como JUnit
	// Inyección de dependencias de Spring, contenedor web Tomcat
	// Completado automático de nombres de método en Eclipse, hibernate, Struts2, etc.
	// porque ConcreteClass no está disponible en tiempo de compilación
	concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass

// para tipos primitivos, clases envoltorio y matrices
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() devuelve el nombre canónico de la clase subyacente. Ten en cuenta que java.lang.Class utiliza Genéricos, lo que ayuda a los frameworks a asegurarse de que la Clase obtenida es una subclase de la Clase Base del framework. Echa un vistazo al Tutorial de Java Generics para aprender sobre genéricos y sus comodines.

Obtener Super Clase

El método getSuperclass() en un objeto Clase devuelve la superclase de la clase. Si esta Clase representa la clase Object, una interfaz, un tipo primitivo o void, entonces se devuelve null. Si este objeto representa una clase de array, entonces se devuelve el objeto Clase que representa la clase 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"

Obtener Clases Miembro Públicas

El método getClasses() de una representación de Clase de objeto devuelve un array que contiene objetos de Clase que representan todas las clases públicas, interfaces y enumeraciones que son miembros de la clase representada por este objeto Clase. Esto incluye miembros de clase e interfaz públicos heredados de superclases y miembros de clase e interfaz públicos declarados por la clase. Este método devuelve un array de longitud 0 si este objeto Clase no tiene clases o interfaces miembros públicos, o si representa un tipo primitivo, una clase de array o void.

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

Obtener Clases Declaradas

El método getDeclaredClasses() devuelve un array de objetos de Clase que reflejan todas las clases e interfaces declaradas como miembros de la clase representada por este objeto Clase. El array devuelto no incluye clases declaradas en clases e interfaces heredadas.

//obteniendo todas las clases, interfaces y enumeraciones que se declaran explícitamente en ConcreteClass
Class[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//imprime [clase com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass, 
//clase com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum, 
//clase com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass, 
//clase com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass, 
//clase com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//clase com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interfaz com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

Obtener Clase Declarante

getDeclaringClass() método devuelve el objeto Class que representa la clase en la que fue declarado.

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

Obteniendo Nombre del Paquete

getPackage() método devuelve el paquete de esta clase. El cargador de clases de esta clase se utiliza para encontrar el paquete. Podemos invocar el método getName() de Package para obtener el nombre del paquete.

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

Obteniendo Modificadores de Clase

getModifiers() método devuelve la representación entera de los modificadores de la clase. Podemos usar el método java.lang.reflect.Modifier.toString() para obtenerlo en formato de cadena como se utiliza en el código fuente.

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

Obtener Parámetros de Tipo

getTypeParameters() devuelve el array de TypeVariable si hay parámetros de tipo asociados con la clase. Los parámetros de tipo se devuelven en el mismo orden que se declaran.

//Obtener parámetros de tipo (genéricos)
TypeVariable[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable t : typeParameters)
System.out.print(t.getName()+",");

Obtener Interfaces Implementadas

getGenericInterfaces() método devuelve el array de interfaces implementadas por la clase con información de tipo genérico. También podemos usar getInterfaces() para obtener la representación de clase de todas las interfaces implementadas.

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

Obtener Todos los Métodos Públicos

getMethods() método devuelve el arreglo de métodos públicos de la Clase incluyendo los métodos públicos de sus superclases y superinterfaces.

Method[] publicMethods = Class.forName("com.journaldev.reflection.ConcreteClass").getMethods();
//imprime métodos públicos de ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));

Obtener Todos los Constructores Públicos

getConstructors() método devuelve la lista de constructores públicos de la referencia de clase del objeto.

//Obtener todos los constructores públicos
Constructor[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//imprime constructores públicos de ConcreteClass
System.out.println(Arrays.toString(publicConstructors));

Obtener Todos los Campos Públicos

getFields() método devuelve el arreglo de campos públicos de la clase incluyendo los campos públicos de sus superclases y superinterfaces.

//Obtener todos los campos públicos
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//imprime los campos públicos de ConcreteClass, su superclase e interfaces superiores
System.out.println(Arrays.toString(publicFields));

Obtener todas las anotaciones

getAnnotations() método devuelve todas las anotaciones para el elemento, podemos usarlo con clases, campos y métodos también. Ten en cuenta que solo las anotaciones disponibles con reflexión tienen una política de retención de RUNTIME, consulta Tutorial de Anotaciones en Java. Examinaremos esto con más detalle en las secciones siguientes.

java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//imprime [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));
  1. Reflexión de Java para Campos

La API de Reflexión proporciona varios métodos para analizar los campos de una clase y modificar sus valores en tiempo de ejecución; en esta sección, veremos algunas de las funciones de reflexión comúnmente utilizadas para los métodos.

Obtener Campo Público

En la última sección, vimos cómo obtener la lista de todos los campos públicos de una clase. La API de Reflexión también proporciona un método para obtener un campo público específico de una clase a través del método getField(). Este método busca el campo en la referencia de clase especificada y luego en las superinterfaces y luego en las superclases.

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

La llamada anterior devolverá el campo de BaseInterface que está implementado por ConcreteClass. Si no se encuentra ningún campo, se genera una excepción NoSuchFieldException.

Clase Declarante del Campo

Podemos usar getDeclaringClass() del objeto campo para obtener la clase que declara el 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();
}

Obtener Tipo de Campo

El método getType() devuelve el objeto Class para el tipo de campo declarado, si el campo es de tipo primitivo, devuelve el objeto de clase envoltorio.

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

Obtener/Establecer Valor de Campo Público

Podemos obtener y establecer el valor de un campo en un objeto utilizando reflexión.

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

El método get() devuelve un objeto, por lo que si el campo es de tipo primitivo, devuelve la correspondiente Clase Envoltorio. Si el campo es estático, podemos pasar null como objeto en el método get(). Existen varios métodos set*() para establecer un objeto en el campo o establecer diferentes tipos de valores primitivos en el campo. Podemos obtener el tipo de campo e invocar la función correcta para establecer el valor del campo correctamente. Si el campo es final, los métodos set() lanzarán java.lang.IllegalAccessException.

Obtener/Establecer Valor de Campo Privado

Sabemos que los campos y métodos privados no pueden ser accesibles fuera de la clase, pero utilizando reflexión podemos obtener/establecer el valor del campo privado desactivando la comprobación de acceso de Java para los modificadores de campo.

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//desactivar la comprobación de acceso con la llamada al método siguiente
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. Reflexión de Java para Métodos

Usando la reflexión, podemos obtener información sobre un método y también podemos invocarlo. En esta sección, aprenderemos diferentes formas de obtener un método, invocar un método y acceder a métodos privados.

Obtener Método Público

Podemos usar getMethod() para obtener un método público de una clase, debemos pasar el nombre del método y los tipos de parámetros del mismo. Si el método no se encuentra en la clase, la API de reflexión busca el método en la superclase. En el siguiente ejemplo, estoy obteniendo el método put() de HashMap usando la reflexión. El ejemplo también muestra cómo obtener los tipos de parámetros, los modificadores del método y el tipo de retorno de un método.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
// obtener tipos de parámetros del método, imprime "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
// obtener tipo de retorno del método, devuelve "class java.lang.Object", referencia de clase para void
System.out.println(method.getReturnType());
// obtener modificadores del método
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"

Invocando Método Público

Podemos usar el método invoke() del objeto Method para invocar un método, en el siguiente código de ejemplo estoy invocando el método put en HashMap usando reflexión.

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}

Si el método es estático, podemos pasar NULL como argumento de objeto.

Invocando Métodos Privados

Podemos usar getDeclaredMethod() para obtener el método privado y luego desactivar la verificación de acceso para invocarlo, el siguiente ejemplo muestra cómo podemos invocar method3() de BaseClass que es estático y no tiene parámetros.

//invocando método privado
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
  1. Reflexión de Java para Constructores

La API de Reflexión proporciona métodos para obtener los constructores de una clase para analizar y podemos crear nuevas instancias de clase invocando el constructor. Ya hemos aprendido cómo obtener todos los constructores públicos.

Obtener Constructor Público

Podemos usar el método getConstructor() en la representación de clase del objeto para obtener un constructor público específico. El siguiente ejemplo muestra cómo obtener el constructor de ConcreteClass definido anteriormente y el constructor sin argumentos de HashMap. También muestra cómo obtener el arreglo de tipos de parámetros para el constructor.

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
// obteniendo parámetros del constructor
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 "[]"

Instanciar Objeto Usando Constructor

Podemos usar el método newInstance() en el objeto constructor para instanciar una nueva instancia de la clase. Dado que utilizamos la reflexión cuando no tenemos la información de las clases en tiempo de compilación, podemos asignarlo a Object y luego usar la reflexión para acceder a sus campos e invocar sus métodos.

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
// obteniendo parámetros del constructor
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. Reflexión sobre Anotaciones

Las anotaciones fueron introducidas en Java 1.5 para proporcionar información de metadatos de la clase, métodos o campos, y ahora se utilizan ampliamente en frameworks como Spring e Hibernate. La API de reflexión también se extendió para proporcionar soporte para analizar las anotaciones en tiempo de ejecución. Utilizando la API de reflexión, podemos analizar las anotaciones cuya política de retención es Runtime. Ya he escrito un tutorial detallado sobre anotaciones y cómo podemos utilizar la API de reflexión para analizar anotaciones, así que te sugiero que eches un vistazo al Tutorial de Anotaciones en Java. Eso es todo para el tutorial de ejemplo de reflexión en Java, espero que te haya gustado y hayas entendido la importancia de la API de Reflexión en Java.

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