Reflexão

Antes de começar por explicar de que se trata a reflexão, talvez seja melhor perceber o que é a Meta Programação. A Meta Programação consiste na escrita de programas que manipulam ou escrevem outros programas. Pode parecer um conceito um pouco esquisito à primeira vista, mas softwares como sejam debuggers ou softwares de profile são exemplos clássicos de meta programas largamente utilizados.

Os meta programas são escritos em meta linguagens, enquanto que os programas manipulados são escritos em linguagem objecto.

A reflexão não é mais nem menos que, a capacidade de uma linguagem ter como meta linguagem ela própria. Ou seja, com a reflexão uma entidade tem a capacidade de manipular a representação da sua estrutura e comportamento durante a sua própria execução.

A reflexão pode existir em dois graus diferentes mas que podem coexistir, a introspecção e a intercessão.

A introspecção é a capacidade que um sistema tem de observar a sua própria estrutura e comportamento. Como exemplo de introspecção podemos ter a determinação do tipo de um objecto em runtime.

Por outro lado, a intercessão é a capacidade do sistema de modificar a sua estrutura e comportamento. Um exemplo é querermos mudar a classe de uma determinada instância.

Ao usarmos a reflexão, seja introspecção ou intercessão, queremos poder interagir com os componentes do sistema como se fossem objectos normais da nossa linguagem. Queremos poder dizer “quais são os métodos da classe X?” ou “de que classe é esta instância?”. Isto é conseguido porque existe o conceito de reificação.

A reificação trata-se da transposição de um conceito abstracto para um objecto acessível para o programador. Se pretendemos perguntar a uma classe quais os seus métodos, então temos de ter disponível um objecto que represente uma classe, e assim por diante.

O Java começou sem nenhumas capacidades de reflexão, na evolução para a versão 1.1 foi introduzido o package java.lang.reflect [1], que passou a possibilitar um mecanismo de reflexão para a linguagem ainda sem grande capacidade. Essa capacidade tem vindo a ser aumentada com o lançamento de novas versões da linguagem. Actualmente o Java permite introspecção estrutural, mas é muitíssimo limitado em termos de intercessão. Esta limitação existe sobretudo porque quando uma classe Java é carregada para a máquina virtual já não pode ser alterada, todas as alterações têm de ser feitas antes do carregamento. Existem ferramentas com esse objectivo, a mais conhecida é talvez o Javassist [2]. O Javassist consegue meter-se antes do carregamento da classe e fazer alterações ao seu bytecode.

Vamos então ver um exemplo do uso da reflexão do Java. Imaginemos que se quer mostrar os métodos não privados de uma determinada classe:

import java.lang.reflect.*;
 
public void mostrarMetodos(Class c) {
    System.out.println(c + " {");
 
    //Vamos pedir os metodos que não são privados da classe
    for (Method m : c.getDeclaredMethods()) {
        if (! Modifier.isPrivate(m.getModifiers())) {
            System.out.println(" " + m);
        }
    }
    System.out.println("}");
}

Neste exemplo estamos efectivamente a usar meta objectos que representam os objectos da linguagem Java. Um desses meta objectos é a Class. Para cada tipo, seja primitivo ou referência, há uma instância desta classe para representar esse tipo. Este meta objecto (java.lang.Class) possui diversos métodos para a reflexão, uns exemplos são [3]:

  • String getName() Retorna o nome da entidade.
  • boolean isInterface() Verifica se o tipo representado é uma interface.
  • boolean isPrimitive() Verifica se o tipo representado é primitivo.
  • int getModifiers() Retorna os modificadores para o tipo (codificado em inteiro) representado.
  • Class getSuperclass() Retorna a classe que representa a superclasse, da classe representada
  • Field[] getFields() Retorna um array contendo os Fields que representam os fields de acesso publico da classe ou interface.
  • Method[] getMethods()– Retorna um array contendo os Métodos que representam os métodos de acesso público da classe ou interface.

São muito diversas as aplicações possíveis para o uso da reflexão, um exemplo interessante era fazermos um inspector de objectos. Um inspector de objectos é basicamente uma ferramenta de depuração, que apresenta uma descrição visual do estado dos objectos do nosso programa. A ideia era o nosso programa chamar o inspector com um determinado objecto a inspeccionar:

MyObject m = new MyObject();
 
Inspector inspector = new Inspector();
inpector.inspect(m);

Então ia ser listada informação sobre o objecto m inspeccionado. Informação como os fields existentes, métodos aplicáveis, etc. Vamos começar então por listar os fields de um objecto:

     for(Field field : m.getClass().getDeclaredFields()){
            //Aqui vamos buscar os modificadores do field (public, private...)
            System.out.print(modToString(field.getModifiers())+" ");
 
            //Aqui vamos buscar o tipo e o nome do field
            System.out.print(field.getType().getCanonicalName()+" ");
            System.out.print(field.getName()+" = ");
            //Finalmente vamos buscar o valor do field
            try{
                field.setAccessible(true);
                System.out.println(field.get(object).toString());
            } catch(IllegalAccessException iae){
                System.out.println(iae+": Não tem permissão.");
                throw new RuntimeException();
            } catch(NullPointerException npe){
                throw new RuntimeException();
            }
    }

O que acontece é que por vezes um objecto herda fields da sua super classe, então também queremos que esses sejam visíveis quando inspeccionamos o objecto:

    for(Field field : object.getClass().getSuperclass().getFields()){
        System.out.print(modToString(field.getModifiers())+" ");
        System.out.print(field.getType().getCanonicalName()+" ");
        System.out.print(field.getName()+" = ");
 
        try{
            field.setAccessible(true);
            System.out.println(field.get(object).toString());
        } catch(IllegalAccessException iae){
            System.out.println(iae+": Não tem permissão.");
            throw new RuntimeException();
        } catch(NullPointerException npe){
            throw new RuntimeException();
        }
    }

Este exemplo é bastante parecido com o anterior, a única coisa que fizemos foi aceder á super classe do objecto através do método getSuperClass(). Há uma linha em comum nos dois exemplos anteriores que pode merecer alguma curiosidade, é a linha onde aparece field.setAccessible(true). Isto não passa de uma pseudo protecção que o sistema de reflexão do Java tem de maneira a que o programador não possa explicitamente alterar um valor de um field, sem primeiro “dar autorização para tal”. Ou seja é o programador que dá autorização a ele próprio para fazer a alteração.

Agora o desejável seria alterar um determinado valor de um field, também é possível com a reflexão:

    //Carregamos a classe com nome "Foo"
    Class cls = Class.forName("Foo");
 
    //Vamos buscar o field que tem nome "bar" e criamos uma instancia de Foo
    Field f = cls.getField("bar");
    Foo foo = new Foo();
 
    System.out.println("bar = " + foo.d);
 
    //Aqui damos um novo valor ao field
    f.setInt(foo, 1024);
 
    System.out.println("bar = " + foo.d);

Estes exemplos não são nada de mais, comparado com o que se pode fazer com a reflexão do Java. A reflexão do Java tem uma API considerável, que pode ser explorada poupando trabalho ao programador. O poder que se tem sobre esta linguagem não é o mesmo de outras como o Common Lisp, por exemplo, onde existe uma igualdade entre dados e programas, o que permite inspeccionar programas como se fossem dados. Mas o maior problema do sistema de reflexão do Java é definitivamente a parte da intercessão, que é muito limitada.

Conclusões

Com este artigo pretendeu-se que o leitor tenha ficado com a ideia geral do que é a meta programação e a reflexão, e a ligação que existe entre ambas. A reflexão não está, de maneira nenhuma, directamente ligada ao Java. Muitas outras linguagens têm um sistema de reflexão, tão bom ou melhor que o Java. Exemplos disso são o Common List, Smalltalk, Python, etc. [4]

Referências

  1. https://cis.med.ucalgary.ca/http/java.sun.com/docs/books/jls/first_edition/html/1.1Update.html
  2. http://www.csg.is.titech.ac.jp/~chiba/javassist/
  3. http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html
  4. http://en.wikipedia.org/wiki/Reflection_%28computer_science%29