https://www.youtube.com/watch?v=_WreIBoC8lA

A reflexão em programação é

"The ability of a running program to examine itself and its software environment, and to change what it does depending on what it finds." (Livro: Java Reflection in Action)

A reflexão pode ser definida como o processo no qual um programa de computador pode observar e modificar sua própria estrutura e comportamento. A reflexão requer que um programa tenha acesso a sua representação interna que chamamos de metadados. A utilização de reflexão também é conhecida como um tipo de metaprogramação, pois com sua utilização um programa pode realizar computações a respeito dele próprio. Porém, ela ocorre em tempo de execução, diferentemente das macros que é um tipo de metaprogramação em nível sintático.

Em programação orientada a objetos, os metadados são organizados em objetos, chamados de metaobjetos. A capacidade de acessar esses metaobjetos em tempo de execução é chamada de introspeção. Então, a introspecção é um subconjunto da reflexão que permite um programa a obter informações a respeito de si próprio.

Para dar suporte a metaprogramação, uma linguagem precisa permitir que um programa tenha acesso aos elementos que foram usado para codificar as entidades do programa. No caso do Java, ter acesso aos dados da classe, métodos e atributos de um objeto.

Anotações

Anotações são como metadados adicionados as nossas classes, atributos e métodos.

"An annotation is a note attached to a program element (a method, a class, and so on) that expresses some additional information about the element. An annotation expresses some intention of the programmer with respect to the element."

Vale ressaltar que anotações são diferentes de comentários. Em os autores destacam as seguintes diferenças:

Exemplos

Em linguagens dinamicamente tipadas como Python, pode-se usar o conceito de Duck Typing.

<aside> 💡 Este nome vem da seguinte constatação: se um objeto anda como um pato e faz quack como um pato então ele é um pato.

</aside>

Para ilustrar o conceito de duck typing, considere três classes distintas que coincidentemente possuem um método com o mesmo nome (podem ter sido criadas por diferentes programadores, não daria para usar polimorfismo). Neste caso, o método getNome que retorna o nome daquele objeto. Contudo, estas classes fazem parte de uma biblioteca externa, então você pode apenas usar. Caso este cenário ocorra em Python, poderiamos ter as seguintes classes definidas.

class Docente:
    def __init__(self,nome):
        self.nome = nome
    def getNome(self):
        return self.nome

class Discente:
    def __init__(self,nome):
        self.nome = nome
    def getNome(self):
        return self.nome

class Curso:
    def __init__(self,nome):
        self.nome = nome
    def getNome(self):
        return self.nome

Dado estas caraterísticas desta linguagem, logo pode-se criar uma função imprimeNomes que para cada objeto de uma lista ela aplica o método getNome. Elas não precisam ter uma relação entre si, como serem subclasses de uma mesma superclasse. Deste modo, o único pre-requisito desta função é que os métodos da lista implementem este método. Caso contrário, um erro irá ocorrer em tempo de execução.

def imprimeNomes (l):
    for i in l:
        print (i.getNome())

imprimeNomes([Curso ("Engenharia da Computacao"),
     Docente ("Claudio Aroucha"), Discente ("Luiz Fernando")]

Observamos que essa abordagem não é possível ser aplicada em linguagens estaticamente tipadas, como é o caso do Java. Por exemplo, o seguinte método não irá compilar, dado que uma variável (i) do tipo (Object) não possui um método (getNome).

    public static void imprimeNomes (List<Object> lista) {
        for (Object i : lista) {
            // erro de compilacao
            System.out.println (i.getNome()); 
        }
    }

Caso as classes fossem desenvolvidas pelo mesmo programador, uma solução era usar herança e polimorfismo. Porém em Java, podemos usar recursos de metaprogramação nesses casos. Com estes recursos é possível tornar a linguagem mais flexível e dinâmica. Muito dos frameworks Java exploram estes recursos.

Tendo as classes Discente, Docente e Curso implementadas. Podemos então escrever o seguinte método que imprime uma lista de tipos heterogeneos.

    public static void imprimeNomes (List<Object> lista) {
        for (Object i : lista) {
            Class c = i.getClass();
            try {
                Method m = c.getMethod("getNome" );
                String nome = (String) m.invoke(i);
                System.out.println(nome);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

Para vermos um caso mais realista, vamos usar a implementação com o componente JTable feita anteriormente. Lembrem-se que foi necessário criar uma subclasse de abstract table model que sobrescrevia alguns métodos essenciais para o funcionamento correto da JTable. Contudo, codificar essa classe pode ser um trabalho muito repetitivo, abrindo margem para uma diferente modelagem. Para isso iremos usar alguma técnicas como: desenvolvimento para interfaces, generics e metaprogramação. Esse exemplo foi inspirado em um artigo da Caelum: http://www.caelum.com.br/apostila-java-testes-xml-design-patterns/reflection-e-annotations/.

Nesta modelagem foram considerados três artefatos independentes do modelo, que são (GenericTableModel), (AbstractDAO) e (ColunaAnnotation).

Primeiro, foi necessário criar uma anotação que será usado mais a frente para identificarmos quais os atributos que serão mostrados na JTable.

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface ColunaAnnotation {
    String nome();
    int pos();
}

Essa anotação será usado em uma classe do modelo, como por exemplo, filmes:

public class Filme {

    @ColunaAnnotation(nome = "Nome", pos = 2)
    private String nome;
    
    @ColunaAnnotation(nome = "Diretor", pos = 1)
    private String diretor;

    @ColunaAnnotation(nome = "Ano", pos = 0)
    private String ano;
    
    ...
}

Depois, implementamos a (AbstractDAO) como uma interface que implementa as operações conhecidas como CRUD:

// implementa um crud
public interface AbstractDAO<T> {
    void create (T obj);
    List<T> retrieve ();
    void update (T obj);
    void delete (T obj);
}

Essa interface interage com a classe concreta (GenericTableModel). Essa classe estende a classe (AbstractTableModel) e implementa os métodos essenciais para a JTable. Para isso, ele tem acesso a um (AbstractDAO). Quando um objeto é instanciado, o construtor requer uma instância de uma classe concreta que implementa a classe (AbstractDAO). Então, ela carrega os dados usando o método (retrieve).

public class GenericTableModel<T> extends AbstractTableModel {

    private AbstractDAO<T> dao;
    private List<T> dados;
    
    public GenericTableModel (AbstractDAO<T> dao) {
         this.dao = dao;
         dados = dao.retrieve();
    }
    ...
}

Agora é necessário sobrecrever os métodos. O primeiro que iremos codificar é o (getRowCount). Este não requer a utilização de metaprogramação como os demais métodos.

    @Override
    public int getRowCount() {
        return dados.size();
    }

As demais implementações irão usar metaprogramação. Por exemplo, para contar a quantidade de colunas, podemos fazer um algoritmo que conta quantos atributos possuem a anotação (ColunaAnnotation):

...
    @Override
    public int getColumnCount() {
        Object obj = dados.get(0);
        Class  c = obj.getClass();
        int conta = 0;
        for (Field f: c.getDeclaredFields() ) {
            if (f.isAnnotationPresent(ColunaAnnotation.class)  ) {
                conta++;
            }
        }
        return conta;
    }
...

Um algoritmo similar retorna o nome de uma dada coluna. Para isso foi utilizado o atributo (pos) da (ColunaAnnotation).

    @Override
    public String getColumnName(int column) {
        // TODO Auto-generated method stub
        Object obj = dados.get(0);
        Class  c = obj.getClass();
        for (Field f: c.getDeclaredFields() ) {
            if (f.isAnnotationPresent(ColunaAnnotation.class)  ) {
                ColunaAnnotation a = (ColunaAnnotation)f.getAnnotation(ColunaAnnotation.class);
                if (a.pos() == column) {
                    return a.nome();
                }
            }
        }
        return null;
    }

Por fim, o método que retorna o valor dado uma linha e coluna.

   
@Override
	public Object getValueAt(int rowIndex, int columnIndex) {
		Object objeto = dados.get(rowIndex);
		Class c = objeto.getClass();
		Field fields[] = c.getDeclaredFields();
		for (Field f: fields) {
			if (f.isAnnotationPresent(ColunaAnnotation.class)) {
				ColunaAnnotation annotation = f.getAnnotation(ColunaAnnotation.class);
				if (columnIndex == annotation.pos()) {
					String nomeAtributo = f.getName();
					String nomeMetodo = "get" + 
									nomeAtributo.substring(0,1).toUpperCase()
									+ nomeAtributo.substring(1); /// nome getNome
					try {
						Method m = c.getDeclaredMethod(nomeMetodo);
						Object value = m.invoke(objeto);
						return value;
					} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
				}
			}
		}
		return null;
	}

Por fim, podemos implementar o DAO específico, que busca as informações em um banco de dados ou na web. Para testar podemos criar um DAO que retorna alguns dados estáticos:

public class FilmeDAO implements GenericDAO<Filme> {
    @Override
    public List<Filme> retrieve() {
        // TODO Auto-generated method stub
        List<Filme> filmes = Arrays.asList(
                new Filme ("Bastardos Inglorious", "Tarantino", "2015"),
                new Filme ("Ilha do Medo", "Scorcese", "2008")
                );
        return filmes;
    }
    
    ...

Outros métodos

@Override
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		System.out.println(aValue);
		System.out.println(rowIndex);
		System.out.println(columnIndex);
		
		T obj = list.get(rowIndex);
		Class c = obj.getClass();
		Field fields[] = c.getDeclaredFields();
		for (Field f: fields) {
			if (f.isAnnotationPresent(ColunaAnnotation.class)) {
				ColunaAnnotation annotation = f.getAnnotation(ColunaAnnotation.class);
				if (columnIndex == annotation.pos()) {
					// qual é o atributo que eu preciso atualizar
					String nomeAtributo = f.getName();
					String nomeMetodo = "set" + 
									nomeAtributo.substring(0,1).toUpperCase()
									+ nomeAtributo.substring(1); /// nome getNome
					System.out.println(nomeMetodo);
					
					try {
						
						Method m = c.getDeclaredMethod(nomeMetodo, f.getType());
						m.invoke(obj, aValue);
						fireTableCellUpdated(rowIndex, columnIndex);
						
					} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
			}
		}
		
		
	}
@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {

		return true;
	}

@Override
	public Class<?> getColumnClass(int columnIndex) {
		// TODO Auto-generated method stub
		return getValueAt(0, columnIndex).getClass();
	}