Mirror DSL: facilitando o uso da API de reflection

Por Jonas Abreu em 17/11/08

No último domingo foi feito o primeiro release público do projeto Mirror (versão 1.2).

O Mirror é um projeto que tem por objetivo facilitar o uso da Java Reflection API, removendo boa parte da burocracia (como as diversas checked exceptions que são lançadas) e utilizando uma DSL para melhorar a legibilidade do código.

Com essa remoção de burocracia e a DSL, é possível transformar o seguinte código:

Field toSet = null;
for (Field f : target.getClass().getDeclaredFields()) {
    if (f.getName().equals("field")) {
        toSet = f;
    }
}
if (toSet != null && ((toSet.getModifiers() & Modifier.STATIC== 0)
        && ((toSet.getModifiers() & Modifier.FINAL== 0)) {
    toSet.setAccessible(true);
    toSet.set(target, value);
}

em algo mais legível e expressivo:

Mirror.on(target).set().field("fieldName").withValue(value);

Atualmente o Mirror possui suporte para lidar com as operações reflectivas mais comuns (como instanciar objetos, invocar métodos, ler ou escrever atributos, etc). Ele foi desenvolvido por Adriano Almeida, Diego Feitosa e eu, todos consultores/instrutores aqui da Caelum, enquanto enfretavamos problemas comuns no dia a dia.

Esperamos que possa ser útil para vocês também!

Nova apostila: Algoritmos e Estruturas de Dados com Java

Por Rafael Cosentino em 20/02/08

A Caelum está lançando hoje mais uma apostila disponível gratuitamente para download: Algoritmos e Estruturas de Dados em Java.

Ela vem sendo formulada há algum tempo, baseada na experiência do curso de versão do IME USP “Algoritmos e Estruturas de dados” que é ministrado por Paulo Silveira, Guilherme Silveira e por mim (Rafael Cosentino) desde 2005.

Abordamos as principais estruturas de dados como Listas, Pilhas, Filas, Tabela de Espalhamento (Hash) e Mapas, e os algoritmos para a manipulação das mesmas. Há ainda tópicos como Recursão, Ordenação (selection-sort, insertion-sort, quick-sort e merge-sort), Pesquisa (sequencial e binária), Busca em Largura, Busca em Profundidade e Consumo de Tempo.


Lista ligada na memória

Normalmente, o desenvolvedor tem um conhecimento superficial sobre as estruturas de dados, sabendo mais ou menos como utilizar as que já estão prontas, sem saber como elas funcionam por dentro, e o que é muito pior, sem saber para quais tipos de problemas cada estrutura é eficiente. Esse conhecimento superficial não basta para desenvolver uma boa aplicação.

Na apostila, mostramos casos em que fica claro como a escolha de uma estrura errada para um determinado problema pode prejudicar o desempenho de uma aplicação ou até mesmo tornar inviável a utilização da mesma. Com alguns exemplos e só um pouquinho de teoria de Ciência da Computação veremos que alguns problemas levariam séculos (séculos de verdade!!!) para serem resolvidos pelo computador se a escolha da estrutura fosse errada e alguns segundos com uma estrutura adequada. E iremos mais afundo, veremos que uma implementação ruim de uma determinada estrutura pode obter resultados desastrosos.


Tabela de espalhamento (hash)

Também, salientaremos a idéia do reaproveitamento. Não precisamos reinventar a roda: mostramos, por exemplo, como implementar uma Pilha ou uma Fila reaproveitando uma Lista. Além disso tudo, mostraremos as implementações do Java para as estrutura de dados do nosso curso. Essas implementações são bem semelhantes as que iremos fazer do zero durante o curso.

O material ainda está na versão beta, ainda faltam alguns poucos tópicos, algumas figuras e um pouco de texto. Além da qualidade técnica, estamos investindo muito na qualidade visual do material; o Tiago Allen Marques de Oliveira, nosso desinger, está preparando imagens fantásticas para apostila.

A Collection genérica: métodos que recebem Object

Por Paulo Silveira em 15/04/07

Os desenvolvedores costumam levar um susto com as assinaturas dos métodos das interfaces do framework das coleções pós-java5: muito mais difícil do que o monte de Es, supers, extends, ?s e &s são os métodos que ainda recebem Object como argumento.

Um exemplo é o método contains. Apesar dele estar na interface parametrizada Collection<E> ele recebe Object em vez de E! Parece um paradoxo: a vantagem do generics não seria exatamente ter uma tipagem mais forte nesses casos, em especial em classes containers? Algumas pessoas acham que é por compatibilidade binária, mas não é, porque a erasure de um método que recebe T é Object, e isso manteria a compatibilidade.

Para entender melhor a decisão da Sun em cada uma dessas assinaturas de métodos você sempre precisa levar em conta dois pontos: erasure e o uso do coringa. Antes vamos relembrar esses conceitos. Considere o código que imprime uma lista de objetos Comparable:

  void imprime(List<Comparable> lista) {
    for (Comparable c : lista)
      System.out.println(c);
  }

Podemos passar para esse método uma List<Comparable>. E uma List<String>? Não podemos, porque uma List<String> não é uma List<Comparable>. Se isso fosse possível, poderíamos adicionar objetos Comparable que não apenas Strings dentro de uma List<String>, quebrando o contrato! Para resolver esse problema, mudamos a assinatura do nosso método para:

  void imprime(List<? extends Comparable> lista) {
    for (Comparable c : lista)
      System.out.println(c);
  }

Agora podemos passar uma List<String>, pois isso é uma List<? extends Comparable>, isto é, uma lista de algum tipo que é compatível com Comparable. Porque então não usamos sempre esse idiomismo? Repare então no seguinte método:

  public void adicionaString(List<Comparable> lista) {
    lista.add("caelum");    
  }

Aqui temos o mesmo problema. Não podemos receber como referência uma List<String>! Vamos tentar aplicar o mesmo procedimento:

  public void adicionaString(List<? extends Comparable> lista) {
    lista.add("caelum")// não podemos invocar métodos que recebem o tipo
      // parametrizado (a menos que seja passado null)
  }

O código acima não compila! Isso ocorre porque List<? extends Comparable> pode receber uma lista de qualquer tipo que seja Comparable. Se isso fosse possível, alguém poderia passar como argumento uma List<Integer>, e no fim do método esta lista de inteiro estaria contendo uma String, quebrando o contrato novamente!

Como resolver esse problema? Não tem como! Se você recebe um objeto parametrizado pelo coringa ?, você nunca poderá invocar um método desse objeto, porque você não sabe qual o tipo que ele realmente aceita! A linguagem não permite isso porque não sabe exatamente o que você vai fazer com esse objeto. Em alguns casos, semanticamente, apesar de um método receber um tipo parametrizado, não tem problema ele receber um objeto que não esteja de acordo com seu tipo parametrizado. Esse é exatamente o caso do método contains: não haveria problema de passar um Integer para o contains de uma List<String>! Mas se contains recebesse E, o seguinte código não compilaria:

  public boolean contemCaelum(List<? extends Comparable> lista) {
    return lista.contains("caelum")// ok, recebe Object!
  }

Essa situação é a mesma para outros métodos, como o Collection.remove(Object), e o Map.get(Object). Ambos os casos seriam catastróficos se recebessem o tipo parametrizado como argumento.

A API de coleções traz assinaturas muito mais estranhas. Eu demorei bastante até entender por completo a assinatura de alguns métodos da Collections, como por exemplo o Collections.min:

public static <T extends Object & Comparable<? super T>> T 
    min
(Collection<? extends T> coll);

em vez de simplesmente uma ingênua assinatura como:

public static <T extends Comparable<T>> T min(Collection<T> coll);

Fica aí o desafio para você também queimar neurônios…

Generics, inferência de tipos e reificação no Java 7

Por Paulo Silveira em 08/04/07

Muito tem se falado sobre o Java 7 SE e que mudanças na linguagem essa versão poderá trazer. Alex Miller criou um excelente post com muito links para JSRs e blogs sobre as propostas para a linguagem. Peter Ahe publicou também um interessante wish list.

Maneiras alternativas de criarmos coleções usando uma sintaxe mais enxuta já foram discutidas por Stephen Colebourne, Peter Ahe e Rafael Ferreira. Gostaria de salientar que o Java 5 já trouxe alguns métodos interessantes na Collections com essa finalidade. Exemplos são quando queremos criar uma coleção vazia ou com apenas um elemento:

List<String> x = Collections.emptyList();
List<Integer> y = Collections.singletonList(5);

Existem métodos análogos para a criação de conjuntos e mapas. A assinatura do método emptyList é <T> List<T> emptyList(). É interessante que a linguagem infere <T>, descobrindo que nesse primeiro caso T deve ser String. Mas isso nem sempre acontece:

  static void  metodo(List<String> lista) {
    // faz alguma coisa
  }
  
  // dentro do main:
  metodo(Collections.emptyList());

A invocação acima não compila, pois tenta fazer T como Object! Algumas pessoas acham que isso é um bug, mas o pessoal da Sun decidiu por não tentar inferir o tipo em determinadas situações. Na verdade, o compilador só tenta inferir o tipo em dois específicos casos: na clausula de return ou em uma atribuição.

A especificação da lingaugem Java tem uma sintaxe especial no caso de você querer indicar qual é o tipo que deve ser utilizado em uma invocação de método genérico:

  metodo(Collections.<String>emptyList());

Pronto! Agora o Java sabe que T deve ser String nessa invocação, e nosso código passa a compilar. Uma sintaxe esdruxula e pouco comum.

Enquanto todo mundo comenta sobre as closures do Java 7, sem dúvida meu principal interesse sobre o Java 7 é a reificação de tipos genéricos, isso é, em tempo de execução, poder determinar os tipos parametrizados de um objeto. Hoje em dia isso não é possível por causa da erasure, que mantém a compatibilidade com o código pré-generics: um objeto não sabe, em tempo de execução, quais são os tipos que foram usados nos seus parâmetros durante sua instanciação. Isso cria algumas clássicas questões nos fóruns, como “porque não posso criar uma array de T?” ou “Porque não posso usar T.class?“. Muitas pessoas consideram até a hipótese de quebrar a compatibilidade binária do Java, e fazem propostas interessantes para resolver esse problema.

Creio que o Java 7 trará muitas novas surpresas, algumas interessantes como as closures, outras infames como o suporte a literais XML dentro da linguagem. Só espero que eles continuem com a premissa do Java, de deixá-la legível e simples, e não façam como no C++, onde todo novo recurso interessante foi adicionado, tornando a linguagem de difícil legibilidade em alguns casos.

Como não aprender orientação a objetos: Herança

Por Paulo Silveira em 14/10/06

Já falei sobre os problemas que os getters e setters podem trazer, caso usados de maneira indiscriminada. A bola da vez é a herança.

Lembro quando comecei a programar profissionalmente em Java, no final de 2000. Estava em um projeto juntamente com o Tiago Silveira, e algo que eu adorava fazer era que meus beans estendessem de HashMap quando tivessem propriedades dinâmicas. O Tiago vivia me criticando, dizendo que eu deveria ter um HashMap e não ser um HashMap. Eu não entendi o porquê de ter muito mais trabalho para escrever métodos que apenas delegariam, em vez de já herdar tudo! Tão simples, e herança parecia se encaixar tão bem nesse caso.

Depois de um tempo comecei a sofrer bastante: tipos errados eram passados como argumento, minha classe possuia métodos que eu não queria e também nao era para tratá-la como um mapa. Seria o eu o único a cometer essa atrocidade apenas para economizar um pouquinho de código? E a resposta vem do java 1.0:

class Stack extends Vector 
class Properties extends Hashtable

Claramente um Properties não deveria ser um Hashtable, afinal ela não mapeia objetos a objetos, mas acaba nos fornecendo um método put(Object, Object), que se usarmos sem passar Strings vai causar problemas. Como resolver isso depois que já nos comprometemos com a herança? A única solução é colocar um aviso grande no javadoc para ninguém usar determinados métodos herdados! O mesmo vale para a Stack e o Vector. Uma Stack não é um Vector, definitivamente. Se você pensar direitinho, uma pilha tem comportamento oposto da implementação da Vector!

Se um gato possui raça e patas, e um cachorro possui raça, patas e tipoDoPelo, logo Cachorro extends Gato? Pode parecer engraçado, mas é o mesmo caso que os anteriores: herança por preguiça, por comodismo, por que vai dar uma ajudinha. A relação “é um” não se encaixa aqui, e vai nos gerar problemas.

Em vários itens do livro Effective Java, Joshua Bloch cita o uso de heraça e de membros package-default e protected. O item 12 diz “minimize o acesso a suas classes e membros“, o item 15 diz “desenhe suas classes pensando no uso de herança, caso contrário proíba-a” e o item 16 “prefira interfaces a classes abstratas“.

Mas sem dúvida o principal é o item 14: “prefira composição em vez de herança“, onde Joshua Bloch diz com todas as letras que herança quebra o encapsulamento. E isso não é novidade, essa conclusão é atribuída a Alan Snyder, no artigo entitulado Encapsulation and Inheritance in Object-Oriented Programming Languages, que data de 1986! Faz incríveis 20 anos que alguém percebeu que “… na maioria das linguagens a introdução da herança compromete seriamente os benefícios do encapsulamento …” (traduzido livremente do abstract do artigo citado). Se você preferir uma opinião mais atual, pode ler esse post do Martin Fowler, que tem menos de um mês.

No livro, Joshua Bloch da como exemplo a criação de uma classe filha de HashSet, e mostra que, sem conhecer profundamente o código fonte da classe mãe, fica impossível de que essa classe filha funcione corretamente ao reescrever um método que é aparentemente inofensivo. A partir do momento que você precisa conhecer o código fonte da sua mãe, você quebrou o encapsulamento. Mais ainda: quando a classe mãe precisar sofrer alguma modificação, o desenvolvedor precisa estar ciente que pode quebrar o funcionamento de várias classes filhas, que pressupunham determinado comportamento interno. Leitura recomendadíssima.

Existem outros exemplos mais sutis de mau uso de herança. Um deles é quando criamos um DAO que é mãe de todos os DAOs. Nesse meu post sobre DAO genérico o Fábio Kung fez um bom comentário sobre isso, onde usamos herança apenas com o intuito de economizar meia dúzia de linhas, sendo que a relação “é um” não existe.

O outro exemplo é HttpServlet e sua própria Servlet. Se você já estendeu de HttpServlet sentiu o problema do método service. Acontece que a implementação padrão do método service em HttpServlet chama o método doGet, doPost, doPut, etc de acordo com o método http utilizado. Esses métodos possuem uma implementação padrão que lançam uma exception. Se você sobrescreve o método service de uma serlvet , você não pode chamar o super.service, pois caso o chame, ele chamará o do correspondente, jogando uma exception. Portanto jamais chame super.service. Já no caso dos métodos init(ServletConfig) e destroy, por exemplo, você deve chamar o super pois não sabemos se a sua classe pai tem algum recurso a ser iniciado e/ou liberado. Sendo assim, em alguns casos você deve, e em outros não deve chamar o super, e você só vai saber disso quando conhecer o código fonte de sua classe pai HttpServlet (ou ler a documentação sobre esse comportamento estranho), quebrando o princípio de encapsulamento. É fácil de reconhecer classes que apresentam esse mesmo problema: o javadoc delas costuma detalhar bastante como o método funciona, e muitas vezes até mostrar o código fonte deles!! Procure-as no jdk, elas não são poucas.

Quem conhece um pouco a API de servlets sabe que eles tentaram resolver o problema adicionando um outro método init, que não recebe parâmetro, e esse sim deve ser reescrito e não precisamos chamar o super. Em outras palavras, uma gambiarra.

Aqui cabe lembrar o que o Joshua Bloch falou: “Crie suas classes pensando em herança, ou então proiba-a“. Parece que HttpServlet não cai nesse caso… Minha proposta para a HttpServlet seria bem simples: você poderia implementar alguma interface, digamos PostProcessor, GetProcessor, entre outras, e registra-la na HttpServlet (um método setPostProcessor, ou passando para o construtor, ou ainda no XML). A interface PostProcessor poderia ser parecida com:

public interface PostProcessor {
  void init(ServletConfig config);
  void doPost(HttpServletRequest request, HttpServletResponse response);
  void destroy();
}

Se sua classe quisesse aceitar tanto GET quanto POST, bastaria implementar ambas interfaces. Interfaces! O caminho é desse lado.

Repare que sempre podemos substituir herança através de um refatoramento simples: extração de uma interface com os métodos herdados, somado a criação de uma implementação que simplesmente delega esses métodos para a antiga classe mãe. Quando então usar herança? Essa é uma questão difícil. Na minha visão particular, a resposta seria um enfático “quase nunca”. Creio que a resposta aqui fique um pouco a critério de cada desenvolvedor, mas sempre com muita cautela!