Exemplo de Tutorial de Reflexão em Java

Reflexão em 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 em Java, podemos inspecionar uma classe, interface, enum, obter informações sobre sua estrutura, métodos e 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 campos.

Reflexão em 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 em Java para Anotações

  1. Reflexão em Java

A reflexão em Java é um conceito muito poderoso e é de pouco uso na programação normal, mas é a espinha dorsal da maioria dos frameworks Java e J2EE. Alguns dos frameworks que utilizam reflexão em 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, saiba mais em Injeção de Dependência Spring
  3. Tomcat contêiner web para encaminhar a solicitação para o módulo correto ao analisar seus arquivos web.xml e URI de solicitação.
  4. Eclipse autocompletar nomes de métodos
  5. Struts
  6. Hibernate

A lista é interminável e todos eles usam reflexão Java porque todos esses frameworks não têm conhecimento e acesso de 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.

  • Desempenho Ruim – Como a reflexão Java resolve os tipos dinamicamente, envolve processamento como varredura no 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 o 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 ser uma séria ameaça à segurança e fazer com que sua aplicação se comporte anormalmente.
  • Manutenção Elevada – O código de reflexão é difícil de entender e depurar, também qualquer problema com o código não pode ser encontrado 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

No Java, cada objeto é ou um tipo primitivo ou uma referência. Todas as classes, enums, e arrays 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, 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 do 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 BaseClassInnerClass{}
		
	//membro enum público
	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{}
	
	//membro enum
	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	//interface de membro
	public interface ConcreteClassPublicInterface{}

}

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

Obter Objeto 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
	//auto completamento de nomes de método 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. Observe que java.lang.Class usa Generics, o 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 Classe retorna a superclasse 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 que representa 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 Membro Públicas

O método getClasses() de uma representação de Classe de objeto retorna um array contendo objetos de Classe representando todas as classes públicas, interfaces e enumerações 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 um array de comprimento 0 se este objeto Classe não tiver classes ou interfaces de membros públicos ou se este objeto Classe representar um tipo primitivo, uma classe de array 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

O método getDeclaredClasses() retorna um array de objetos de Classe refletindo todas as classes e interfaces declaradas como membros da classe representada por este objeto Classe. O array retornado 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 de classe, podemos usar java.lang.reflect.Modifier.toString() método para obtê-lo no formato de string como 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 algum parâmetro de tipo associado à classe. Os parâmetros de tipo são retornados na mesma ordem que 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 de classe de todas as 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()));		

Obtenha Todos os Métodos Públicos

getMethods() método retorna a matriz 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));

Obtenha Todos os Construtores Públicos

getConstructors() método retorna a lista de construtores públicos da classe referência do objeto.

//Obtenha 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));

Obtenha Todos os Campos Públicos

getFields() método retorna a matriz 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 super interfaces
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 anotações disponíveis com reflexão estão com política de retenção de RUNTIME, confira Tutorial de Anotações Java. Iremos examinar isso com mais detalhes nas seções posteriores.

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 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 iremos examinar algumas das funções de reflexão comumente utilizadas 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 método para obter um campo público específico de uma classe através do método getField(). Este método procura pelo campo na classe especificada e então nas super interfaces e depois nas super classes.

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

A chamada acima retornará o campo da BaseInterface que é implementada pela ConcreteClass. Se nenhum campo for encontrado, então ele lança NoSuchFieldException.

Classe Declarante do Campo

Podemos usar getDeclaringClass() do objeto de 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 Class 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 Wrapper correspondente. Se o campo for estático, podemos passar um Objeto como nulo no método get(). Existem vários métodos set*() para definir um Objeto no campo ou definir diferentes tipos de tipos primitivos no 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 desligando a verificação de acesso do Java para modificadores de campo.

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//desligando a 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 o método `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 pelo 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 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 um 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 o método getDeclaredMethod() para obter o método privado e então desativar a verificação de acesso para invocá-lo, o exemplo abaixo mostra como podemos invocar o método3() da classe 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 do HashMap. Também mostra como obter o array 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 construtor para instanciar uma nova instância da classe. Como usamos reflexão quando não temos informações das classes em tempo de compilação, podemos atribuí-la a um Object e então usar reflexão adicionalmente 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 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 analisá-las, então sugiro que você confira o Tutorial de Anotações em Java. Isso é tudo para o tutorial de exemplo 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