Exemplo de Tutorial de Reflexão em Java

Reflexão Java fornece a capacidade de inspecionar e modificar o comportamento em tempo de execução da aplicação. A reflexão em Java é um dos tópicos avançados do núcleo do Java. Usando a reflexão Java, podemos inspecionar uma classe, interface, enum, obter sua estrutura, métodos e informações de campos em tempo de execução, mesmo que a classe não seja acessível em tempo de compilação. Também podemos usar a reflexão para instanciar um objeto, invocar seus métodos e alterar valores de campo.

Reflexão Java

  1. Reflexão em Java
  2. Reflexão Java para Classes
  3. Reflexão Java para Campos
  4. Reflexão Java para Métodos
  5. Reflexão Java para Construtores
  6. Reflexão Java para Anotações

  1. Reflexão em Java

Reflexão em Java é um conceito muito poderoso e de pouca utilidade na programação normal, mas é a espinha dorsal da maioria dos frameworks Java e J2EE. Alguns dos frameworks que utilizam reflexão Java são:

  1. JUnit – usa reflexão para analisar a anotação @Test para obter os métodos de teste e então invocá-los.
  2. Spring – injeção de dependência, leia mais em Injeção de Dependência no Spring
  3. Tomcat container web para encaminhar a solicitação para o módulo correto, analisando os arquivos web.xml e URI da solicitação.
  4. Eclipse autocompletar de nomes de métodos
  5. Struts
  6. Hibernate

A lista é interminável e todos eles usam reflexão em Java, porque todos esses frameworks não têm conhecimento e acesso a classes definidas pelo usuário, interfaces, seus métodos, etc. Não devemos usar reflexão na programação normal, onde já temos acesso às classes e interfaces, devido aos seguintes inconvenientes.

  • Baixo Desempenho – Como a reflexão em Java resolve os tipos dinamicamente, ela envolve processos como a varredura do classpath para encontrar a classe a ser carregada, causando baixo desempenho.
  • Restrições de Segurança – A reflexão requer permissões em tempo de execução que podem não estar disponíveis para o sistema em execução sob um gerenciador de segurança. Isso pode fazer com que sua aplicação falhe em tempo de execução devido ao gerenciador de segurança.
  • Problemas de Segurança – Usando reflexão, podemos acessar partes do código às quais não deveríamos ter acesso, por exemplo, podemos acessar campos privados de uma classe e alterar seu valor. Isso pode representar uma séria ameaça à segurança e fazer com que sua aplicação se comporte de maneira anormal.
  • Alta Manutenção – O código de reflexão é difícil de entender e depurar, além de quaisquer problemas com o código não poderem ser encontrados em tempo de compilação, porque as classes podem não estar disponíveis, tornando-o menos flexível e difícil de manter.
  1. Reflexão Java para Classes

Em Java, todo objeto é ou um tipo primitivo ou uma referência. Todas as classes, enumerações e matrizes são tipos de referência e herdam de java.lang.Object. Os tipos primitivos são – boolean, byte, short, int, long, char, float e double. java.lang.Class é o ponto de entrada para todas as operações de reflexão. Para cada tipo de objeto, o JVM instancia uma instância imutável de java.lang.Class que fornece métodos para examinar as propriedades em tempo de execução do objeto e criar novos objetos, invocar seus métodos e obter/configurar campos de objeto. Nesta seção, vamos analisar os métodos importantes da Classe, para conveniência, estou criando algumas classes e interfaces com hierarquia de herança.

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");
	}
	
	// classe pública interna
public class
	public class BaseClassInnerClass{}
		
	
// membro público enum
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;
	}
	
	
// classes internas

	public class ConcreteClassPublicClass{}
	private class ConcreteClassPrivateClass{}
	protected class ConcreteClassProtectedClass{}
	class ConcreteClassDefaultClass{}
	
	
// enum membro

	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	
// interface membro

	public interface ConcreteClassPublicInterface{}

}

Vamos dar uma olhada em alguns dos métodos de reflexão importantes para classes.

Obter Objeto de Classe

Podemos obter a Classe de um objeto usando três métodos – através da variável estática class, usando o método getClass() do objeto e java.lang.Class.forName(String fullyClassifiedClassName). Para tipos primitivos e arrays, podemos usar a variável estática class. As classes Wrapper fornecem outra variável estática TYPE para obter a classe.


// Obter Classe usando reflexão

Class concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
	
// o método abaixo é usado na maioria das vezes em frameworks como JUnit

	
// injeção de dependência Spring, contêiner web Tomcat

	
// conclusão automática de métodos Eclipse, hibernate, Struts2 etc.

	
// porque ConcreteClass não está disponível em tempo de compilação

	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, classes wrapper e arrays
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() retorna o nome canônico da classe subjacente. Note que java.lang.Class utiliza Generics, que ajuda os frameworks a garantir que a Classe recuperada seja uma subclasse da Classe Base do framework. Confira o Tutorial sobre Generics em Java para aprender sobre generics e seus wildcards.

Obter Super Classe

getSuperclass() método em um objeto Class retorna a super classe da classe. Se esta Classe representar a classe Object, uma interface, um tipo primitivo ou void, então null é retornado. Se este objeto representar uma classe de array, então o objeto Classe representando a classe Object é retornado.

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"

Obter Classes de Membros Públicos

O método getClasses() de uma representação de Classe de objeto retorna uma matriz contendo objetos de Classe representando todas as classes públicas, interfaces e enums que são membros da classe representada por este objeto Classe. Isso inclui membros de classe e interface públicos herdados de superclasses e membros de classe e interface públicos declarados pela classe. Este método retorna uma matriz de comprimento 0 se este objeto Classe não tiver classes ou interfaces de membro públicas ou se este objeto Classe representar um tipo primitivo, uma classe de matriz ou void.

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

Obter Classes Declaradas

getDeclaredClasses() método retorna uma matriz de objetos de Classe refletindo todas as classes e interfaces declaradas como membros da classe representada por este objeto Classe. A matriz retornada não inclui classes declaradas em classes e interfaces herdadas.

//obtendo todas as classes, interfaces e enums que são explicitamente declarados em ConcreteClass
Class[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//imprime [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, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

Obter Classe Declarante

getDeclaringClass() método retorna o objeto Class que representa a classe na qual foi 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());

Obtendo Nome do Pacote

getPackage() método retorna o pacote para esta classe. O carregador de classes desta classe é usado para encontrar o pacote. Podemos invocar o método getName() de Package para obter o nome do pacote.

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

Obtendo Modificadores de Classe

getModifiers() método retorna a representação int dos modificadores da classe, podemos usar o método java.lang.reflect.Modifier.toString() para obtê-lo no formato de string, conforme usado no código-fonte.

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())); 

Obter Parâmetros de Tipo

getTypeParameters() retorna a matriz de TypeVariable se houver parâmetros de tipo associados à classe. Os parâmetros de tipo são retornados na mesma ordem em que são declarados.

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

Obter Interfaces Implementadas

getGenericInterfaces() método retorna a matriz de interfaces implementadas pela classe com informações de tipo genérico. Também podemos usar getInterfaces() para obter a representação da classe de todas as interfaces implementadas.

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

Obter Todos os Métodos Públicos

getMethods() método retorna o array de métodos públicos da classe, incluindo métodos públicos de suas superclasses e 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));

Obter Todos os Construtores Públicos

getConstructors() método retorna a lista de construtores públicos da classe referenciada pelo objeto.

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

Obter Todos os Campos Públicos

getFields() método retorna o array de campos públicos da classe, incluindo campos públicos de suas superclasses e superinterfaces.

// Obter todos os campos públicos
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
// imprime os campos públicos de ConcreteClass, sua superclasse e superinterfaces
System.out.println(Arrays.toString(publicFields));

Obter Todas as Anotações

getAnnotations() método retorna todas as anotações para o elemento, podemos usá-lo com classe, campos e métodos também. Note que apenas as anotações disponíveis com reflexão são com política de retenção de RUNTIME, confira Tutorial de Anotações em Java. Vamos analisar isso com mais detalhes nas seções seguintes.

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

A API de Reflexão fornece vários métodos para analisar os campos da classe e modificar seus valores em tempo de execução, nesta seção vamos explorar algumas das funções de reflexão comumente usadas para métodos.

Obter Campo Público

Na última seção, vimos como obter a lista de todos os campos públicos de uma classe. A API de Reflexão também fornece um método para obter um campo público específico de uma classe por meio do método getField(). Este método procura pelo campo na referência da classe especificada e, em seguida, nas superinterfaces e depois nas superclasses.

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

A chamada acima retornará o campo de BaseInterface implementado por ConcreteClass. Se nenhum campo for encontrado, ele lança NoSuchFieldException.

Classe Declarante do Campo

Podemos usar getDeclaringClass() do objeto do campo para obter a classe que declara o 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();
}

Obter Tipo de Campo

O método getType() retorna o objeto da classe para o tipo de campo declarado; se o campo for do tipo primitivo, ele retorna o objeto da classe wrapper.

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

Obter/Definir Valor de Campo Público

Podemos obter e definir o valor de um campo em um Objeto usando reflexão.

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

O método get() retorna um Objeto, então se o campo for do tipo primitivo, ele retorna a classe de invólucro correspondente Wrapper Class. Se o campo for estático, podemos passar null como Objeto no método get(). Existem vários métodos set*() para definir um Objeto para o campo ou definir diferentes tipos de tipos primitivos para o campo. Podemos obter o tipo do campo e então invocar a função correta para definir o valor do campo corretamente. Se o campo for final, os métodos set() lançam java.lang.IllegalAccessException.

Obter/Definir Valor de Campo Privado

Sabemos que campos e métodos privados não podem ser acessíveis fora da classe, mas usando reflexão podemos obter/definir o valor do campo privado desativando a verificação de acesso java para modificadores de campo.

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//desativando verificação de acesso com a chamada do método abaixo
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. Reflexão Java para Métodos

Usando reflexão, podemos obter informações sobre um método e também podemos invocá-lo. Nesta seção, aprenderemos diferentes maneiras de obter um método, invocar um método e acessar métodos privados.

Obter Método Público

Podemos usar getMethod() para obter um método público de uma classe, precisamos passar o nome do método e os tipos de parâmetros do método. Se o método não for encontrado na classe, a API de reflexão procura o método na superclasse. No exemplo abaixo, estou obtendo o método put() de HashMap usando reflexão. O exemplo também mostra como obter os tipos de parâmetros, modificadores de método e o tipo de retorno de um método.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
// obter tipos de parâmetros do método, imprime "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
// obter tipo de retorno do método, retorna "class java.lang.Object", referência de classe para void
System.out.println(method.getReturnType());
// obter modificadores de método
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"

Invocando Método Público

Podemos usar o método invoke() do objeto Method para invocar um método, no código de exemplo abaixo estou invocando o método put em HashMap usando reflexão.

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 o método for estático, podemos passar NULL como argumento do objeto.

Invocando Métodos Privados

Podemos usar getDeclaredMethod() para obter o método privado e depois desativar a verificação de acesso para invocá-lo, o exemplo abaixo mostra como podemos invocar o método3() da BaseClass que é estático e não tem 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. Reflexão Java para Construtores

A API de Reflexão fornece métodos para obter os construtores de uma classe para análise, e podemos criar novas instâncias da classe invocando o construtor. Já aprendemos como obter todos os construtores públicos.

Obter Construtor Público

Podemos usar o método getConstructor() na representação da classe do objeto para obter um construtor público específico. O exemplo abaixo mostra como obter o construtor da ConcreteClass definida acima e o construtor sem argumentos de HashMap. Também mostra como obter a matriz de tipos de parâmetros para o construtor.

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//obtendo parâmetros do construtor
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 Construtor

Podemos usar o método newInstance() no objeto do construtor para instanciar uma nova instância da classe. Como usamos reflexão quando não temos informações sobre as classes em tempo de compilação, podemos atribuí-las a Object e depois usar reflexão para acessar seus campos e invocar seus métodos.

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//obtendo parâmetros do construtor
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. Reflexão para Anotações

As anotações foram introduzidas no Java 1.5 para fornecer informações de metadados da classe, métodos ou campos e agora são amplamente utilizadas em frameworks como Spring e Hibernate. A API de reflexão também foi estendida para fornecer suporte para analisar as anotações em tempo de execução. Usando a API de reflexão, podemos analisar as anotações cuja política de retenção é Runtime. Já escrevi um tutorial detalhado sobre anotações e como podemos usar a API de reflexão para analisar as anotações, então sugiro que você confira o Tutorial de Anotações Java. Isso é tudo para o exemplo de tutorial de reflexão em Java, espero que tenha gostado do tutorial e entendido a importância da API de Reflexão em Java.

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