Java e Reflection

Daniel Destro

Como utilizar essa poderosa API do java para saber, em tempo de execução, informações sobre classes que você não tinha conhecimento prévio. Como invocar e construir objetos destas classes?



Usando Java Reflection

Hoje a tecnologia Java proporciona inúmeras funcionalidades que fazem frente à qualquer outra tecnologia, e uma das coisas mais fascinantes e produtivas do Java é a Reflection, uma característica intrínseca desta tecnologia e que existe em poucas outras como existe nessa. Ela está presente no Java desde a versão 1.1.

A Reflection permite um programa Java examinar ou fazer a introspecção nele mesmo, ou seja, olhar e examinar suas propriedades e estrutura. Com isso, você pode, por exemplo obter o nome de todos os membros de uma classe, como atributos e métodos, bem como executar um método usando a Introspection.

É deste modo que os aplicativos de ambiente de desenvolvimento (as IDEs) conseguem examinar e exibir a estrutura e conteúdo das classes e beans.

Para este tutorial, é necessário que você tenha uma boa familiarização com o Java, e em especial, que conheça o básico da classe java.lang.Class, a qual guarda informações sobre uma determinada classe. Este tutorial, diferente dos outros, apresentará o código mais completo, ao invés de trechos, já que dependemos do código inteiro para mostrar o que estamos "refletindo". Então não se assuste com o tamanho dos códigos.

Para compreendermos melhor esta feature do Java, vamos dar uma olhada no códigos de exemplo que se seguem.

Listando os métodos de uma classe

01 import java.lang.reflect.*;
02 
03 public class ReflectionTeste {
04 
05     public static void main(String args[]) {
06         try {
07             Class c = Class.forName"java.lang.String" );
08             Method m[] = c.getDeclaredMethods();
09             for (int i = 0; i < m.length; i++) {
10             System.out.printlnm[i].toString() );
11         }
12     }
13         catch (Throwable e) {
14             System.err.println(e);
15         }
16     }
17 }


O código acima é bem simples e sua função é de apenas listar os métodos da classe java.lang.String, ou melhor, ele lista a assinatura dos métodos da classe, como o tipo de retorno e os parâmetros do método, com a estrutura completa dos pacotes dos tipos (fully qualified type).

Carregamos a classe com o método Class.forName e então, como o método getDeclaredMethods pegamos uma lista com os métodos. Ainda, com o método getMethods, lista os métodos herdados das classes pai (superclasse) da classe que está sendo observada.


Instanceof - Comparação de tipos
Usando Reflection é ainda possível fazer o uso do instanceof, de uma maneira diferente da tradicional. Vejamos:

1 Class cls = Class.forName("java.lang.String");
2 
3 boolean b1 = cls.isInstance(new Integer(37));
4 System.out.println(b1);
5 
6 boolean b2 = cls.isInstance(new java.lang.String("teste"));
7 System.out.println(b2);


Obtendo informações mais precisas sobre os métodos
Indo mais a fundo no uso da Reflection, podemos fazer uma maior detalhamento dos métodos membros da classe analisada, ou seja, para cada método examinado podemos verificar os modificadores, o nome do método, o tipo de retorno, os parâmetros do método e as exceções que o método pode lançar. Vejamos código de exemplo:

01 import java.lang.reflect.*;
02 
03 public class Classe1 {
04 
05     private int funcao1Object p, int throws NullPointerException {
06         if (p == null)
07             throw new NullPointerException();
08         return x;
09     }
10 
11     public static void main(String args[]) {
12         try {
13             Class cls = Class.forName("Classe1");
14             Method methlist[] = cls.getDeclaredMethods();
15 
16             for (int i = 0; i < methlist.length; i++) {
17                 Method m = methlist[i];
18                 System.out.println("nome = " + m.getName());
19                 System.out.println("membro da classe = " + m.getDeclaringClass());
20                 System.out.println("modificador = " + Modifier.toStringm.getModifiers() ));
21                 Class pvec[] = m.getParameterTypes();
22 
23                 for (int j = 0; j < pvec.length; j++)
24                     System.out.println("parâmetro #" + j + " " + pvec[j]);
25 
26                 Class evec[] = m.getExceptionTypes();
27                 for (int j = 0; j < evec.length; j++)
28                     System.out.println("exceção #" + j + " " + evec[j]);
29 
30                 System.out.println("tipo de retorno = " + m.getReturnType());
31                 System.out.println("-----");
32             }
33         }
34         catch (Throwable e) {
35             System.err.println(e);
36         }
37     }
38 }


Este simples código realmente "depena" um método, retornando toda a informação sobre ele.



Construtores
Com a Reflection ainda é possível obter informação sobre os construtores da classe analisada. Neste caso não há informação sobre o tipo de retorno, pois os construtores de classe não definem um tipo de retorno (óbvio). Veja o código:

01 import java.lang.reflect.*;
02 
03 public class Constructor1 {
04 
05     public Constructor1() { }
06   
07     protected constructor1(int i, double d) { }
08   
09     public static void main(String args[]) {
10         try {
11             Class cls = Class.forName("constructor1");
12             Constructor ctorlist[] = cls.getDeclaredConstructors();
13 
14             for (int i = 0; i < ctorlist.length; i++) {
15                 Constructor ct = ctorlist[i];
16                 System.out.println("nome = " + ct.getName());
17                 System.out.println("membro da classe = " + ct.getDeclaringClass());
18         
19                 Class pvec[] = ct.getParameterTypes();
20                 for (int j = 0; j < pvec.length; j++)
21                     System.out.println("parâmetro #" + j + " " + pvec[j]);
22 
23                 Class evec[] = ct.getExceptionTypes();
24                 for (int j = 0; j < evec.length; j++)
25                     System.out.println("exceção #" + j + " " + evec[j]);
26             }
27         }
28         catch (Throwable e) {
29             System.err.println(e);
30         }
31     }
32 }


Atributos
É possível ainda fazer uma introspecção afim de obter os dados e informações sobre os atributos membros da classe, como os modificadores, tipo e o nome dos atributos. Conforme o código:

01 import java.lang.reflect.*;
02 
03 public class TesteAtributos {
04 
05     private double d;
06     public static final int i = 37;
07     String s = "testing";
08   
09     public static void main(String args[]) {
10         try {
11             Class cls = Class.forName("TesteAtributos");
12       
13             Field fieldlist[] = cls.getDeclaredFields();
14             for (int i = 0; i < fieldlist.length; i++) {
15                 Field fld = fieldlist[i];
16                 System.out.println("nome atributo = " + fld.getName());
17                 System.out.println("membro da classe = " + fld.getDeclaringClass());
18                 System.out.println("tipo = " + fld.getType());
19                 int mod = fld.getModifiers();
20                 System.out.println("modificadores = " + Modifier.toString(mod));
21             }
22         }
23         catch (Throwable e) {
24             System.err.println(e);
25         }
26     }
27 }



Invocando métodos pelo nome
Uma parte interessantíssima do Java Reflection é invocar um método pelo seu nome, ou seja, fornecendo o nome do método para a execução. Veja o exemplo abaixo:

01 import java.lang.reflect.*;
02 
03 public class InvocaMetodo {
04 
05     public int umMetodo(int a, int b) {
06         return a + b;
07     }
08   
09     public static void main(String args[]) {
10         try {
11             Class partypes[] new Class[2];
12             partypes[0= Integer.TYPE;
13             partypes[1= Integer.TYPE;
14 
15             Class cls = Class.forName("InvocaMetodo");
16             Method meth = cls.getMethod("umMetodo", partypes);
17 
18             Object arglist[] new Object[2];
19             arglist[0new Integer(37);
20             arglist[1new Integer(47);
21 
22             InvocaMetodo methobj = new InvocaMetodo();
23             Object retobj = meth.invoke(methobj, arglist);
24             Integer retval = (Integer)retobj;
25             System.out.println(retval.intValue());
26         }
27         catch (Throwable e) {
28             System.err.println(e);
29         }
30     }
31 }


No exemplo acima o método getMethod é usado para achar o método da classe pelo nome informado ("umMetodo") contendo os parâmetros informados. Uma vez achado o método e capturado pelo objeto to tipo Method, ele é invocado sob uma instância do objeto do seu tipo correto. Uma lista de parâmetros também deve ser passada, correspondentes aos parâmetros e tipos do método requisitado.

As exceções que o Method.invoke() pode lançar são diversas: uma é a respeito de segurança, se, por exemplo, você tentou executar um método private, uma IllegalAccessException é lançada; e outra exceção ocorre no caso de que o próprio método lance uma exceção, esta exceção vem então encapsulada dentro de uma InvocationTargetException. Caso você tenha mandado os métodos errados para o invoke, uma IllegalArgumentException é lançada.

Criando novos objetos
É muito comum, que, dado um objeto da classe Class, nós precisarmos de uma instância dessa classe. Java também permite isso, invocando os construtores de uma classe com os parâmetros desejados.

01 import java.lang.reflect.*;
02 
03 public class TesteConstrutor {
04 
05     public TesteConstrutor() { }
06   
07     public TesteConstrutor(int a, int b) {
08         System.out.println("a = " + a + " b = " + b);
09     }
10   
11     public static void main(String args[]) {
12         try {
13             Class cls = Class.forName("TesteConstrutor");
14 
15             Class partypes[] new Class[2];
16             partypes[0= Integer.TYPE;
17             partypes[1= Integer.TYPE;
18 
19             Constructor ct = cls.getConstructor(partypes);
20 
21             Object arglist[] new Object[2];
22             arglist[0new Integer(37);
23             arglist[1new Integer(47);
24 
25             Object retobj = ct.newInstance(arglist);
26         }
27         catch (Throwable e) {
28             System.err.println(e);
29         }
30     }
31 }


Se você quisesse chamar o construtor default de uma classe, isto é, o que não recebe nenhum parâmetro, basta você utiliza o método newInstance() da própria classe Class.



Alterando o valor dos campos
Outro uso da Reflection é para se alterar o valor dos campos de dados dos objetos. Esse nome pode ser achado pelo seu nome, via Reflection em um programa em execução e então ter o seu valor alterado. Veja no exemplo:

01 import java.lang.reflect.*;
02 
03 public class AlteraValorCampo {
04 
05     public double d;
06   
07     public static void main(String args[]) {
08         try {
09             Class cls = Class.forName("AlteraValorCampo");
10             Field fld = cls.getField("d");
11             field2 f2obj = new field2();
12             System.out.println("d = " + f2obj.d);
13             fld.setDouble(f2obj, 12.34);
14             System.out.println("d = " + f2obj.d);
15         }
16         catch (Throwable e) {
17             System.err.println(e);
18         }
19     }
20 }


Usando arrays
Um último uso do Reflection em Java é a criação e manipulação de arrays. Em Java, arrays são uns tipos de dados especializados, e uma referência a uma array pode ser referenciado a uma referência do tipo Object. para ver como Reflection trabalha com arrays, veja a seguir:

1     Class cls = Class.forName("java.lang.String");
2     Object arr = Array.newInstance(cls, 10);
3     Array.set(arr, 5"Isto é um teste");
4     String s = (String)Array.get(arr, 5);
5     System.out.println(s);


Neste exemplo é criado um array de 10 objetos String e para a posição 5 do array ele seta uma instância de String.

Sumário
Java Reflection é muito útil pois ele suporta a recuperação das informações das classes e estruturas de dados pelo nome, e ainda permita a sua manipulação por um programa Java em execução. Essa é uma poderosa ferramente para desenvolvimento de solução muito genéricas e que não é encontrada em muitas outras tecnologias.

Lembre-se que reflection é algo relativamente lento (melhoraram o desempenho no jdk1.4), e muito "error-prone" (sucetível a erro), então você deveria evitar usá-la, a não ser que realmente seja necessário.



Apoiado e desenvolvido por Caelum Cursos Java - Copyright © 2001-2008 GUJ