O Reflection, em poucas palavras, serve para determinar métodos e atributos que serão utilizados de determinada classe (que você nem conhece) em tempo de execução. Há inúmeras utilidades para esse tipo de tarefa, podemos citar por exemplo a instalação de plugins adicionais ao nosso software desenvolvido.

Imagine se toda vez que um novo plugin fosse desenvolvido para o Eclipse, ele tivesse que ser refatorado (refactoring) a fim de adaptar-se ao uso desse novo plugin. Seria muito trabalhoso e inviável, dado a quantidade de plugins que são desenvolvidos para tal IDE. A solução para isso é sem dúvida o uso do Reflection, com ele podemos deixar, por exemplo, pontos estratégicos do sistema para aceitar a instalação de plugins afim de tornar o sistema extensível.


Class<ClassDesconhecida> classe = ClassDesconhecida.class;
for (Field atributo : classe.getDeclaredFields()) {
  System.out.println(atributo.getName());      
}
Listagem 1. Aplicando Reflection

Perceba no código acima que não sabemos quais os métodos e atributos da “ºClassDesconhecida”, para isso vamos utilizar as ferramentas oferecidas pelo Reflection afim de descobrir essas informações em tempo de execução. Veja que com o método “ºDeclaredFields” conseguimos capturar todos os atributos da classe em questão, mesmo sem saber como ela foi construída.

Riscos do Java Reflection

Deve usar este recurso com muito cuidado, sempre atentando aos possíveis riscos que podem ser causados pelo uso indevido do mesmo:

  • Redução de Desempenho, pois se o código que esta sendo executado for requerido com muita frequência, consequentemente acarretará em uma redução de desempenho.
  • Problemas por restrição de segurança, especialmente se executado em um ambiente com regras específicas
  • Exposição de estrutura interna dos objetos, pois utilizando este recurso temos acesso a todos os métodos e atributos de determinado objeto, isso pode ser um problema em se tratando de segurança.

Benefícios do Java Reflection

Mas, além dos riscos temos também os benefícios que podem ser aproveitados se usarmos o Java Reflection da forma correta e adequada.

  • Facilidade na manutenção
  • Minimização de Erros
  • Ganho de Produtividade
  • Padronização
  • Extensibilidade

Alguns itens são bem óbvios, como é o caso do Ganho de Produtividade, isso porque é notório que não precisaremos ficar reescrevendo código a toda vez que quisermos adicionar novas funcionalidades ao nosso sistema, é óbvio que o mesmo tem que estar pronto para receber tal “acoplamento”. Consideramos a extensibilidade como um dos pontos mais importantes e beneficiários do Java Reflection.

Pense que utilizando o Java Reflection da maneira correta nosso sistema pode tornar-se mais complexo (do ponto de vista de funcionalidades) apenas adicionando plugins sem precisar mexer em nenhuma linha de código, isso é incrível e extremamente poderoso.

Podemos citar aqui até alguns exemplos conhecidos, usam a ideia da reflexão para tornar o seu software extensível, é o caso de ferramentas como Hibernate, Netbeans, Eclipse e entre outras que possibilitam que seja instalados novos plugins sem necessidade de alteração do código da aplicação principal. O Hibernate por exemplo utiliza do Java Reflection para descobrir quais os campos da nossa classe afim de criar as tabelas no banco (com o uso do auto-update='true').

Vamos no exemplo abaixo criar uma classe Arquivo com vários campos e depois aplicar o Reflection nela.


public class Arquivo {

	private String nome;
	private String numeroDeCCP;
	private int quantidadeCCP;
	private String hashParaTabelaUnica;
	private boolean realizarCheckSum;
	private boolean converterHash;
	private boolean conjugarNomeENumeroCCP;
	public String getNome() {
		return nome;
	}
	public void setNome(String nome) {
		this.nome = nome;
	}
	public String getNumeroDeCCP() {
		return numeroDeCCP;
	}
	public void setNumeroDeCCP(String numeroDeCCP) {
		this.numeroDeCCP = numeroDeCCP;
	}
	public int getQuantidadeCCP() {
		return quantidadeCCP;
	}
	public void setQuantidadeCCP(int quantidadeCCP) {
		this.quantidadeCCP = quantidadeCCP;
	}
	public String getHashParaTabelaUnica() {
		return hashParaTabelaUnica;
	}
	public void setHashParaTabelaUnica(String hashParaTabelaUnica) {
		this.hashParaTabelaUnica = hashParaTabelaUnica;
	}
	public boolean isRealizarCheckSum() {
		return realizarCheckSum;
	}
	public void setRealizarCheckSum(boolean realizarCheckSum) {
		this.realizarCheckSum = realizarCheckSum;
	}
	public boolean isConverterHash() {
		return converterHash;
	}
	public void setConverterHash(boolean converterHash) {
		this.converterHash = converterHash;
	}
	public boolean isConjugarNomeENumeroCCP() {
		return conjugarNomeENumeroCCP;
	}
	public void setConjugarNomeENumeroCCP(boolean conjugarNomeENumeroCCP) {
		this.conjugarNomeENumeroCCP = conjugarNomeENumeroCCP;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (conjugarNomeENumeroCCP ? 1231 : 1237);
		result = prime * result + (converterHash ? 1231 : 1237);
		result = prime
				* result
				+ ((hashParaTabelaUnica == null) ? 0 : hashParaTabelaUnica
						.hashCode());
		result = prime * result + ((nome == null) ? 0 : nome.hashCode());
		result = prime * result
				+ ((numeroDeCCP == null) ? 0 : numeroDeCCP.hashCode());
		result = prime * result + quantidadeCCP;
		result = prime * result + (realizarCheckSum ? 1231 : 1237);
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Arquivo other = (Arquivo) obj;
		if (conjugarNomeENumeroCCP != other.conjugarNomeENumeroCCP)
			return false;
		if (converterHash != other.converterHash)
			return false;
		if (hashParaTabelaUnica == null) {
			if (other.hashParaTabelaUnica != null)
				return false;
		} else if (!hashParaTabelaUnica.equals(other.hashParaTabelaUnica))
			return false;
		if (nome == null) {
			if (other.nome != null)
				return false;
		} else if (!nome.equals(other.nome))
			return false;
		if (numeroDeCCP == null) {
			if (other.numeroDeCCP != null)
				return false;
		} else if (!numeroDeCCP.equals(other.numeroDeCCP))
			return false;
		if (quantidadeCCP != other.quantidadeCCP)
			return false;
		if (realizarCheckSum != other.realizarCheckSum)
			return false;
		return true;
	}

}
Listagem 2. Definindo nossa Classe Arquivo

Perceba que temos tudo em nossa classe: Atributos, ºgetters, ºsetters, hashCode e ºequals. Agora vamos trabalhar em cima dela e usar o reflection para mostrar tudo que ela tem. Conforme vamos apresentar o código abaixo, você poderá acompanhar o que esta sendo feito passo a passo com os comentários do código que estão bem detalhados e explicados.


import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Principal {

	public static void main(String args[]) {				 
		
		/*
		 * Carregamos a classe Arquivo através do Class.forName que nos possibilita
		 * carregar uma classe através de uma dada string que deve corresponder ao local
		 * onde a classe está, além disso, por padrão a classe é carregada no ClassLoader
		 * que está sendo utilizado pela classe que está executando o comando.
		 * */
		Object arquivoFromReflection = null;
		try {
			arquivoFromReflection = Class.forName("Arquivo").newInstance();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//Recupera o nome da classe
		System.out.println("Nome da Classe: "+arquivoFromReflection.getClass().getName());
		
		/*
		 * A Classe Method do Reflection nos da a possibilidade de manusear
		 * todos os métodos dentro do objeto carregado 
		 * */
		System.out.println("");
		System.out.println("Métodos: ");
		for(Method m : arquivoFromReflection.getClass().getMethods()){
			System.out.print(m.getName()+", ");
		}
		
		/*
		 * Vamos agora capturar os atributos da classe. Temos agora outra classe 
		 * muito importante para uso do Reflection, a classe Field. Esta nos permite
		 * manusear os campos/fields da nossa classe carregada.
		 * */
		System.out.println("");
		System.out.println("Atributos: ");
		for(Field f : arquivoFromReflection.getClass().getDeclaredFields()){
			System.out.print(f.getName()+", ");
		}
		
		/*
		 * Perceba que nossa abordagem é bem simples, ou seja, estamos capturando apenas
		 * os nomes dos métodos e atributos, mas você pode ir muito além, capturando os 
		 modificadores, 
		 * tipos, retorno e etc.
		 * */
	}
}
Listagem 3. Mostrando atributos e métodos com Reflection

SAÍDA: NomedaClasse:Arquivo

Métodos: equals, hashCode, getNome, setNome, getNumeroDeCCP, setNumeroDeCCP, getQuantidadeCCP, setQuantidadeCCP, getHashParaTabelaUnica, setHashParaTabelaUnica, isRealizarCheckSum, setRealizarCheckSum, isConverterHash, setConverterHash, isConjugarNomeENumeroCCP, setConjugarNomeENumeroCCP, wait, wait, wait, toString, getClass, notify, notifyAll

Atributos: nome, numeroDeCCP, quantidadeCCP, hashParaTabelaUnica, realizarCheckSum, converterHash, conjugarNomeENumeroCCP

Conclusão

O uso dessa poderosa ferramenta faz-se necessária principalmente quando estamos pensando em extensibilidade, tornar o sistema apto a receber novas funcionalidades sem a necessidade de modificação do código.