Wednesday, May 20, 2009

Mapas em Java - Reduzindo o sofrimento

O aparecimento de linguagens modernas como o Groovy e o Ruby me levou a uma triste constatação: Java é uma linguagem completamente defasada que pouco colabora para a produtividade do programador. Antes que alguém comece a jogar pedras, quero deixar claro que estou falando da linguagem, não da JVM, mesmo porque outras linguagens como Groovy e JRuby rodam na JVM. Quando comecei a estudar Groovy, o recurso que mais me impressionou foi o excelente suporte a coleções e mapas. Estas estruturas são de uso muito frequente, mas o Java simplesmente não tem suporte nativo para elas, já que a manipulação de listas e mapas é feita via API do JDK. Se você nunca usou listas e mapas numa linguagem como Groovy ou Ruby, você provavelmente está relativamente satisfeito com a API de collections do Java. É mais ou menos como alguém que sempre teve um carro 1.0 e não sabe o quanto ele é fraco até o dia que dirige um carro 1.8. Nada melhor que um exemplo para ilustrar:
Em Groovy:
// Definindo e inicializando um mapa em Groovy
def errorMap = [
 1:"Database is down"
 ,2:"Network is down"
 ,3:"Something else"
]
Em Java:
//Mesmo mapa em Java
Map errorMap = new HashMap();

errorMap.put(1, "Database is down");
errorMap.put(2, "Network is down");
errorMap.put(3, "Something else");
Nos dois exemplos anteriores temos quatro linhas de código Java contra apenas uma do Groovy. Se o mapa for atribuído a uma variável estática, que aliás é muito comum no caso de mapas "constantes", a vantagem do Groovy fica ainda maior.
Em Groovy:
// Definindo e inicializando um mapa estático em Groovy
def static errorMap = [
 1:"Database is down"
 ,2:"Network is down"
 ,3:"Something else"
]
Em Java:
//Mesmo mapa estático em Java
static Map errorMap = new HashMap();

static{
 errorMap.put(1, "Database is down");
 errorMap.put(2, "Network is down");
 errorMap.put(3, "Something else");
}
Resumindo, o Java não permite criar e inicializar um mapa ao mesmo tempo, o que acaba levando o programador a escrever muito código simplesmente para criar e preencher um mapa. Esta deficiência em particular poderia não existir se o método Map.put() retornasse a instância do próprio Map, como faz o método StringBuffer.append(), mas infelizmente o retorno do put() é void :(
Para melhorar esta triste realidade, podemos utilizar um MapBuilder, que usa o mesmo pattern do StringBuffer. O método put() do MapBuilder retorna a própria instância do builder, o que permite encadear chamadas ao put numa única linha de código. O HashMap é criado internamente e pode ser obtido através do método toMap().
public class MapBuilder{
  private Map workMap;

  public MapBuilder(){
     workMap = new HashMap();
  }

  public MapBuilder put(Object key, Object value){
     assert key != null;

     workMap.put(key, value);

     return this;
  }

  public toMap(){
     return workMap;
  }
}
A implementação acima é muito simples e pode ser melhorada, mas o objetivo é mostrar o conceito de um map builder. O trecho de código a seguir mostra um exemplo de uso:
static Map errorMap = new MapBuilder()
 .put(1, "Database is down")
 .put(2, "Network is down")
 .put(3, "Something else")
.toMap();
O MapBuilder é uma idéia simples que pode ajudar a reduzir volume e melhorar a legibilidade do código.

3 comments:

Unknown said...

Python também tem um suporte excelente a coleções (mapas, listas e tuplas), com sintaxe fácil para declaração e preenchimento dessas estruturas. Contudo, o recurso que me deixou apaixonado foi map e list comprehension (http://docs.python.org/3.0/tutorial/datastructures.html#list-comprehensions). Simples, fácil de ler e poderoso. :)

Wilson Freitas said...

Verdade. Esqueci de citar Python, que também segue a linha de pouco código pra fazer muita coisa. Espero que o Java 7 traga alguma coisa nova para tornar a linguagem menos verbose. Na minha opinião, a única melhoria significativa feita no Java em todos esses anos foram as annotations do Java 5.

nf said...

Wilson e Matheus, sejam bem-vindos ao mundo das linguagens práticas! >;^)