Blog do Urubatan
msgbartop
Desenvolvedor, Palestrante, Escritor, Nerd Assumido e Pai do Marcus :D
msgbarbottom

13 May 09 Java tem espelhos, e o mago deve saber jogar com eles (Básico da Reflexão)

Ok, o título deste post ficou meio estranho, mas como muita gente diz que isto é magia negra mesmo, então até que o título não esta tão ruim :D
Uma coisa que eu vejo bastante por ai, e não é de hoje, é que grande parte dos programadores Java não faz idéia do que seja Reflection, e normalmente tem medo de escrever, ou até mesmo de ler código “complicado”.
Não vou dizer que reflection é simples, mas é um recurso extremamente poderoso do Java que todo programador Java deveria conhecer.

O que é Reflection

Reflection em java, é como o nome diz, a possibilidade de programaticamente, visualizar um reflexo de um objeto ou uma classe, e como em um espelho, é possível também distorcer um pouco esta imagem quando necessário.
Como no reflexo em um espelho, o que você visualiza, não é o objeto real, apenas um reflexo deste, mas como na física, você pode deduzir como interagir com o objeto real, utilizando o seu reflexo.

Quando eu escrevi isto, lembrei de uma cena de um filme muito velho, acho que era “fúria de titâs” ou algo assim, onde alguem utilizava o reflexo da medusa em um escudo para lutar com ela sem se transformar em pedra.

Mas voltando ao assunto, reflection, é a possibilidade, de em tempo de execução, diversas informações sobre um objeto qualquer, incluindo mas não se limitando a seguinte lista:

  • Qual é a classe de um objeto
  • Quais métodos públicos a classe possui
  • Quais métodos foram declarados na classe (incluindo os privados)
  • Quais interfaces a classe implementa
  • Qual classe a classe do objeto estende
  • Quais anotações foram colocadas em uma classe
  • Quais anotações foram colocadas em um método
  • Quais anotações foram colocadas em um atributo
  • Quais anotações foram colocadas em um parâmetro de um método
  • Quais os tipos dos parâmetros de um método
  • Qual o tipo dos atributos de uma classe
  • Qual o tipo de retorno de um método

Via reflexão também é possível por exemplo, executar as seguintes ações em um objeto de uma classe que não existia no momento em que o código foi desenvolvido (um plugin por exemplo):

  • Instanciar um objeto
  • Chamar um método passando parâmetros ou não
  • Ler o valor de atributos privados de um objeto
  • Criar um clone de um objeto copiando o estado do mesmo
  • Chamar diretamente métodos privados de um objeto
  • Chamar métodos estáticos de uma classe
  • Ler atributos estáticos de uma classe

Claro que estes são só exemplos, a API de reflection adiciona muito mais flexibilidade do que isto, principalmente quando combinada com algum framework de AOP ou com a API de criação de Proxies disponível no próprio Java.
Mas para fazer tudo isto, é necessário conhecer algumas classes que a maior parte dos programadores Java não se preocupam em conhecer.
Só um detalhe antes de apresentar as novas classes, apenas combinando a API de reflection com a API de Proxies e o suporte a annotations do Java 5 é possível implementar todos os recursos do EJB3 por exemplo.
Agora vamos aprender a usar espelhos para fazer mágica :D

  • java.lang.Class – Esta é a classe que representa uma classe, todos os objetos tem um método “getClass” que retorna um objeto do tipo java.lang.Class, a partir de um objeto deste tipo, é possível obter diversas informações sobre uma classe, este é o ponto de entrada para o mundo dos espelhos, exatamente como aquele espelho do Alice no pais das maravilhas, mas sem o perigo de ficar preso por la como aconteceu com ela
  • java.lang.reflect.Field – Esta é a classe que representa um atributo de uma classe, objetos deste tipo nos permitem saber por exemplo, o nome dos campos de uma classe, e até mesmo ler o valor destes atributos
  • java.lang.reflect.Method – Objetos deste tipo representam métodos de uma classe, é possível utilizar estes objetos para chamar métodos passando ou não parâmetros para eles, mesmo que estes métodos sejam privados
  • java.lang.reflect.Modifier – Esta classe permite saber se um método ou atributo é privado ou publico, se ele é estático, ou seja, quais os modificadores de acesso foram utilizados na declaração do método, atributo ou classe
  • java.lang.reflect.Array – Esta classe representa um array, com objetos deste tipo é possível acessar todos os elementos de um array por exemplo
  • java.lang.reflect.Constructor – Esta classe representa um construtor de uma classe qualquer, uma classe pode ter diversos contrutores, e com referências para estes contrutores podemos saber quais os parâmetros necessários para instanciar uma objeto, ou até mesmo instanciar o objeto realmente
  • java.lang.reflect.AnnotatedElement – esta interface permite saber quais as anotações presentes em uma declaração, ela é implementada por Class, Method e Field por exemplo
  • Package java.lang.reflect – Antes de continuar lendo, por favor de uma olhada em quais as outras classes disponíveis no pacote responsável pela criação de espelhos e também pela distorção dos reflexos nestes espelhos no Java

Agora um exemplo básico, por que acredito que vocês não conseguiram entender muita coisa até aqui, mas eu prometo que depois de um exemplo as coisas vão ficar um pouco mais claras.

Este exemplo, razoavelmente simples, vai listar todos os métodos e atributos públicos em uma classe.
Para o exemplo se tornar um pouco mais divertido, o nome da classe deve ser passado como parâmetro, isto também faz você poder listar uma classe que não existia quando o programa foi compilado.

test/List.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 
package test;
 
import java.lang.reflect.*;
 
public class List {
        public static void main(String[] args) throws Exception{
                if(args.length!=1)
                        throw new Exception("Voce precisa informar o nome da classe como unico parametro");
                Class<?> clazz = Class.forName(args[0]);
                printClassInformation(clazz);
                printClassAttributes(clazz);
                printClassMethods(clazz);
        }
        public static void printClassInformation(Class<?> clazz) throws Exception {
                System.out.println("Class Name: " + clazz.getName());
        }
        public static void printClassAttributes(Class<?> clazz) throws Exception {
                for(Field f : clazz.getDeclaredFields()){
                        System.out.format("\t--Private Attribute Name: %s, Attribute Type: %s\n",f.getName(),f.getType().getName());
                }
        }
        public static void printClassMethods(Class<?> clazz) throws Exception {
                for(Method m : clazz.getMethods()){
                        System.out.format("\tMethod Name: %s, Return Type: %s, Parameter Types: %s\n",m.getName(),m.getReturnType().getName(),m.getParameterTypes().toString());
                }
        }
}

Compile este exemplo, e execute passando por exemplo “java.lang.Class” como parâmetro e você vai ter um exemplo básico do funcionamento da API de Reflection.
Você pode também criar outra classe, empacotar ela em um arquivo .jar, adicionar este jar no classpath e executar este exemplo passando o nome da sua nova classe como parâmetro.

Claro que este é um exemplo extremamente básico, com um código que se não fosse pelo parâmetro passado para algumas classes, poderia ser executado até no Java 1.2, ou seja, a API de reflexão não é nova, é apenas sub utilizada pelo programador de nível médio.
Ahh, mas saber isto vai fazer com que eu seja um expert em java?
Claro que não! Mas não saber isto, com certeza te impede de ser um :D

test/Call.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
package test;
 
import java.lang.reflect.*;
 
public class Call {
        public static void main(String[] args) throws Exception {
                Class<?> clazz = Class.forName(args[0]);
                Method m = clazz.getMethod(args[1]);
                Object instance = clazz.newInstance();
                Object result = m.invoke(instance);
                System.out.println(result);
        }
}

Este é outro exemplo bastante simples, apenas para demonstrar algumas possibilidades, este exemplo bastante simples, chama um método sem parâmetros em uma classe qualquer que tenha um construtor padrão.
Para chamar métodos estáticos, seria necessária uma pequena alteração, o parâmetro passado para o método “invoke” da classe Method, é a instancia do objeto onde o método deve ser chamado, para métodos estáticos, esta instância é substituida pela classe que possui o método estático.
E o construtor padrão é necessário, por que precisamos de uma instância da classe para invocar o método, se a classe não possuir um construtor padrão, precisaremos passar parâmetros para o construtor, o que iria complicar bastante o exemplo, e a idéia aqui é só mostrar algumas possibilidades, e não complicar mais ainda a vida de vocês.
Mas como funciona o exemplo?
Tente compilar o exemplo, e executar ele passando os parâmetros:
java.lang.Object hashCode
ou
java.lang.Object toString
ou
QualquerNomeDeClasse nomeDeUmMétodoSemParâmetrosDestaClasse
e pronto, método executado.

Ai você vai pensar agora: Mas é muito mais fácil eu escrever direto “System.out.println(new Object().hashCode())” no meu código.
Claro que é, mas para isto você precisaria saber que o método que seria executado era o hashCode de uma nova instância de Object.
A idéia da API de reflection é obter informações em tempo de execução, é possibilitar um pouco de méta programação no Java.

Imagine só, criar um proxy para uma interface que garante que todos os métodos executados, caso tenham a anotação @Transactional, serão executados dentro do contexto de uma transação.
Isto é meta programação, isto é manter a mente um pouco mais aberta do que o programador médio.

Outra possível pergunta: Tu não vai ser expulso do clubinho por que esta revelando os segredos, como aconteceu com o Mister M?
Resposta: Claro que não, isto não é segredo nenhum, tu só não tinha aprendido antes por que não parou para estudar. Se você fosse um pouco mais preguiçoso, como eu, você já teria parado para estudar uma forma de trabalhar menos com as ferramentas que você tem na mão, e se você é um programador, meta programação, é uma forma de fazer mais trabalhando menos.

Mais uma pergunta: Por que ninguem me contou isto antes? explicando assim até parece fácil!
Resposta: Provavelmente achavam que tu é burro demais para entender, agora tu pode provar que isto não é verdade. E não se engane, não é tão simples assim, código usando reflexão pode ficar bastante complicado, eu só mostrei uns exemplos bem básicos.

A API de Proxies eu vou deixar como assunto para um próximo post, minha imagina imaginação esta meio fraca hoje, estou de saco cheio de assistir esta aula maluca que eu não to nem prestando atenção, acho que vou pra casa já :D

Se vocês tiverem idéias de mais exemplos que vocês querem ver como pode ser feito com reflexão, ou se tiverem perguntas sobre reflexão em java, por favor sintam-se a vontade de registrar as perguntas, dúvidas e sugestões aqui nos comentários do blog, vou tentar responder todas as perguntas :D
E como sempre, se você gostou deste post, indique para seus amigos, e coloque um link no seu blog :D

Acho que daqui a uns dias eu escrevo mais sobre reflection, mas vou tentar utilizar uns exemplos mais complexos, se tiverem sugestões para o próximo post, é só deixar nos comentários.

Tags: , , ,