Brincando com Generics: o BizarreGenericDao

Por Paulo Silveira em 29/10/06

Conversando com o Orseni Campos, ele me contou de uma sacada muito interessante que teve juntamente com seu colega Alexandre Bitencourt para resolver um clássico problema do generics: em tempo de execução você não consegue descobrir o tipo parametrizado que foi passado como argumento.

Em outras palavras, repare no código do Dao genérico que foi discutido aqui (estou pulando a interface por questão de simplicidade):

public class Dao<T> {
  // …
  public Dao(Session session, Class<T> persistentClass) {
    this.session = session;
    this.persistentClass = persistentClass;
  }
}

Aí, quando vamos criar um novo dao genérico:

Dao<Livro> dao = new Dao<Livro>(session, Livro.class);

Parece estranho ter de passar a Class que representa o livro, se eu já estou passando essa informação através do tipo parametrizado. Seria interessante fazer algo como:

public class Dao<T> {
  // …
  public Dao(Session session) {
    this.session = session;
    this.persistentClass = T.class;
  }
}

Isso não compila, essa informação (sobre quem é T dentro de Dao) não é armazenada em um .class. O motivo é simples. Só existe uma classe Dao, não importa como ela foi instânciada. Em outras palavras:

(new Dao<Autor>(session, Autor.class)).getClass() == 
   (new Dao<Livro>(session, Livro.class)).getClass()

retorna true. Então o problema é o seguinte: existe apenas uma classe, não importa quantas instâncias de tipo parametrizado diferentes você criar. Não faria sentido algum ter um método como clazz.getTypeArguments() que te devolvesse uma array com os tipos parametrizados que tivessem sido usados na instanciação, já que só existe uma classe para todas as instâncias de diferentes tipos parametrizados. Isso se deve a erasure: em tempo de execução só sabemos que T pode ser um filha de Object, nesse caso.

Mas e se existisse mais de uma classe?

class AutorDao extends Dao<Autor> {
}

class LivroDao extends Dao<Livro> {
}

Obviamente (new LivroDao()).getClass() == (new AutorDao()).getClass() retorna false. Mais que isso, agora você consegue buscar por reflection qual é o parâmetro que foi passado na hora de estender a classe Dao, já que AutorDao e LivroDao possuem essa informação em seus bytecodes:

Class clazz = (Class<T>) ((ParameterizedType
getClass().getGenericSuperclass()).getActualTypeArguments()[0];

Então, se colocamos essa linha dentro da nossa classe mãe:

public class Dao<T> {
  // …
  public Dao(Session session) {
    this.session = session;
    this.persistentClass = (Class<T>) ((ParameterizedType
      getClass().getGenericSuperclass()).getActualTypeArguments()[0];
  }
}

Agora funciona caso utilizemos uma instância de uma filha de Dao que tenha explicitado quem é T. Bem, eu não gostei dessa solução porque nos obrigava a ter uma classe filha, mesmo que vazia, para cada entidade. Mas o Orseni e o Alexandre não desistiram. A proposta deles foi de declarar Dao como abstrata, e na hora de instanciar:

Dao<Livro> dao = new Dao<Livro>(session){};

Repare o {}! Esse código vai gerar uma classe anônima em tempo de compilação, e essa classe sem nome vai ser filha de Dao<Livro>, e agora podemos pegar essa informação por reflection! Isso sim é gambiarra criatividade :). Usar em produção? Acho que não seria elegante. Nem eles estão usando, mas foi um bonito desafio.

Entendendo Unicode e os Character Encodings

Por Paulo Silveira em 22/10/06

Todo mundo já passou por problemas com character encodings. Quem nunca abriu uma conexão JDBC com o MySQL e puxou do banco um monte de caracteres onde em vez de acentos só se viam pontos de interrogação e caracteres estranhos?

O blog do Joel Spolsky já publicou um post sobre esse assunto, que é bem simples e direto. O fato importante é mostar que Unicode não é um encoding. Unicode define codepoints (um número) para cada letra (ou símbolo). Por exemplo, a letra ´A´ é o codepoint 65. A partir do Unicode 3.1, o codepoint pode até mesmo ser maior que 2^16: o fatídico 65536 (atualmente vai até 16*65536, ou seja 0×10FFFF). Sim! Unicode nada mais é que um tabelão! Nas palavras do wikipedia, “… (Unicode) assign a unique number to each character used in the written languages of the world“, traduzindo, Unicode associa um número único para cada caractere usado nas línguas escritas de todo o mundo.

Pois é, unicode não é uma maneira de se representar caracteres com 2 bytes. Aliás, Unicode não é um encoding. A pergunta “Você está usando unicode ou latin1?” está completamente errada. Quem é responsável por codificar um codepoint em bytes é o encoding. Aqui estamos falando de UTF-8, o ISO-8859-1 (vulgo latin1), entre outros. Alguns encodings podem não suportar todos os codepoints possíveis, outros podem tentar economizar alguns bytes quando codificar alguns caracteres (caso do UTF-8).

Mas não quero ficar na teoria, quero passar para o código. Vamos então codificar o ‘ç‘ em diferentes encodings: ISO-8859-1, UTF-8 e UTF-16. Basta colocar o seguinte código no main:

String[] codes = "ISO-8859-1""UTF-8""UTF-16" };
String palavra = "ç";

for (String encoding : codes) {
  byte[] b = palavra.getBytes(encoding);
  System.out.printf("%10s\t%d\t", encoding, b.length);
  for (int k = 0; k < b.length; k++) {
    String hex = Integer.toHexString((b[k256256);
    if (hex.length() == 1)
      hex = "0" + hex;
    System.out.print(hex);
  }
  System.out.println();
}

E ao rodar, teremos em cada linha o encoding, a quantidade de bytes utilizada para codificar o ‘ç‘ e sua representação em hexadecimal codificada.

ISO-8859-1	1	e7
     UTF-8	2	c3a7
    UTF-16	4	feff00e7

Vamos tentar o mesmo para a letra ‘a‘:

ISO-8859-1	1	61
     UTF-8	1	61
    UTF-16	4	feff0061

É interessante reparar aqui que o UTF-8 gastou 2 bytes em um caso, e 1 byte no outro. Outro fato importante é que UTF-8 codifica diversos caracteres da mesma forma que o ISO-8859-1 (e este por sua vez tem uma estrita relação com o US-ASCII). Você pode incrementar esse código e testar com outros encodings, tais como US-ASCII, Cp1252 e UTF-16. Você pode ver quais encodings a sua JVM suporta com Charset.availableCharsets().

Como falei anteriormente, existem caracteres que extrapolam o índice do 65535. Então como ficam esses caracteres no java, já que o char tem apenas 2 bytes? É aí que entram os surrogate pairs: alguns caracteres agora são utilizados para indicar que o restante do caractere ainda está por vir! Alguns problemas surgem com isso: o método length() da String não funciona mais tão bem: ele apenas diz quantos chars aquela String possui.

Para resolver esses problemas novos métodos e classes foram adicionados ao java 5 (através da JSR-204), como o codePointCount, na String. Esse artigo da Sun discute bem esse assunto.

Mas quando ocorrem os problemas de encoding que citamos no começo do post? Um caso em potencial é quando tentamos ler uma sequência de bytes usando um encoding que não foi o que utilizamos para codificar aquela String. Vamos simular isso escrevendo o ‘ç’ em UTF-8 e lendo como ISO-8859-1, e vice-versa:

// ç escrito em UTF-8 mas lido em ISO-8859-1
System.out.println(new String("ç".getBytes("UTF-8")"ISO-8859-1"));
// ç escrito em ISO-8859-1 mas lido em UTF-8
System.out.println(new String("ç".getBytes("ISO-8859-1")"UTF-8"));

E o resultado:

ç
?

Cadê o c cedilha? Você pode não ver, mas ele está por aí! Esses caracteres lhe trazem algumas lembranças?

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!

Lidando com Exceptions

Por Fabio Kung em 07/10/06

Pequena Revisão
Sabemos que as exceções em Java são classificadas em dois tipos:

  • Checked Exceptions: seguem a regra do handle-or-declare. O desenvolvedor é obrigado a tratar (try-catch) ou relançar (throws), caso não saiba como tratar.
  • Unchecked Exceptions: não é obrigatório tratar nem relançar, apesar de ser possível. Caso não haja try-catch adequado à exceção gerada, ela é automaticamente relançada. Geralmente são filhas de java.lang.RuntimeException direta ou indiretamente.

Uma das grandes dificuldades dos desenvolvedores Java é saber como lidar adequadamente com exceções. Perguntas como “Minha exceção deve ser checked ou unchecked?” e “O que escrever no meu bloco catch?” são extremamente comuns. Espero esclarecer um pouco estas dúvidas expondo aqui a minha maneira de agir nestes casos.

Gerando exceções
Aconteceu algo de errado no seu sistema (atingiu um estado inconsistente) e você está louco para fazer throw new Exception(); ?

  • Primeira regra: coloque no mínimo uma mensagem explicativa na sua exceção. Quantas vezes você não ficou nervoso com o seu colega desenvolvedor, que jogou uma exceção sem mensagem nenhuma e não há como ter a mínima idéia do que aconteceu? Melhorando um pouco fica: throw new Exception("Um dos argumentos para o cálculo do desconto é Inválido");
  • Segunda regra: use sempre exceções específicas ao seu caso, isto é, se o problema foi no cálculo de um desconto faça algo do tipo throw new CalculoDeDescontoException("Um dos argumentos é inválido"); e não throw new Exception("Não há como calcular o desconto pois um dos argumentos é inválido");. Aqui você poderia terminar com muitas exceções específicas demais. Como alternativa, poderia então fazer algo como throw new LogicaDeNegocioException("Um dos argumentos para o calculo do desconto é inválido"). Usando sempre o bom senso.

Checked ou Unchecked?
Pare um pouco para pensar no significado de um try-catch:

Uma chance de se recuperar do erro. Uma chance de tratar a exceção.

Tendo isso em mente fica mais fácil decidir entre lançar uma checked exception ou uma unchecked exception. Tente responder a seguinte pergunta:

Quero dar a chance a quem chama meu código de tratar o possível erro?

Se a resposta for sim, use uma checked exception. Neste caso o chamador vai ser obrigado a fazer o try-catch e terá a chance de se recuperar do erro. Não é o que você tanto quis?

Caso ele não saiba como se recuperar do erro, será obrigado a relançar a exceção. Assim, outro lugar terá a chance de se recuperar do erro. No fim das contas, o que uma checked exception trouxe foi a capacidade de recuperação.

Terceira regra: use checked exceptions para erros recuperáveis.

E quanto a erros irrecuperáveis? Nestes casos não queremos dar a chance de recuperação do erro. São casos como “queimou o banco de dados”, ou então NullPointerException, que é sempre culpa do programador (apesar do nosso orgulho dizer o contrário). Deu erro e pronto, não há o que fazer. A única saída é mostrar para o usuário do sistema que aconteceu um problema, uma mensagem do tipo:

Desculpe, ocorreu um erro interno. Contate o administrador.

Como para unchecked exceptions não é obrigatório o handle-or-declare o mais comum é que a exceção vá subindo na pilha de chamadas e seja exibida ao usuário. Aqui uma grande vantagem é que pode existir um componente especialista em lidar com errors irrecuperáveis. Ele poderia interceptar todas as ações do sistema e esperar por erros que ninguém tratou: erros irrecuperáveis. Quando um erro destes chegar, o componente poderia fazer log da ocorrência, fazer rollback da transação e formatar a mensagem adequada ao usuário (não parece muito legal jogar o Stacktrace na cara de um usuário, parece?)

Implementar este componente interceptador não é tão difícil atualmente:

  • Ambiente WEB: recursos como Servlet Filters que interceptam todas as requisições em um ambiente web ou tratamento de erro declarativo pertencente à especificação de servlets/jsp (seção error-page do web.xml).
  • Ambientes desktop: programação orientada a aspectos, proxies ou coisas menos avançadas como o padrão de projeto Decorator.

Quarta regra: use unchecked exceptions para erros irrecuperáveis.

Ei, a API que eu uso não segue estas regras!
Você, como bom samaritano, agora segue as boas práticas. Mas o JDBC por exemplo, não segue. Supondo que você seja obrigado a usá-lo:

try {
  PreparedStatement stmt = con.prepareStatement(query);
  // …
catch (SQLException e) {
  // Epa! SQLException é erro grave, irrecuperável! Pode ter queimado o banco de dados…
  // Não deveria ser checked exception e, além disso, é genérica demais. 
  // Serve para muitos casos!

  throw new AcessoADadosException("Problema na criação do Statement",  e);
  // Agora sim, encapsulei a SQLException na minha exceção específica. 
  // Além disso ela é RuntimeException: unchecked!

}

Neste caso, AcessoADadosException é uma unchecked exception, já que o seu erro é irrecuperável e você não quer dar a chance de ninguém se recuperar dele. Além de que, agora a sua exceção é mais específica e descreve melhor o seu problema, sua API ficou mais limpa: não vão ser mais necessários try-catch(SQLException) nem throws SQLException espalhados pelo seu sistema todo.

De fato, o caso mais comum é o de erros irrecuperáveis. Por isso essa prática de transformar checked exceptions (mal empregadas) em unchecked exceptions têm se tornado comum.

Quinta regra: nunca faça:

catch(Exception e) {
    System.out.println(e);
}

Este código pega muito mais erros do que ele está realmente esperando e o pior, não está preparado para tratá-los. Sempre relance erros que você não está preparado para tratar. Nunca pegue um erro e ignore-o. O rollback de uma transação pode depender da ocorrência de um erro irrecuperável que você acaba de esconder do resto do sistema!

Performance: HashSet em vez de ArrayList

Por Paulo Silveira em 04/10/06

Quando um programador começa com Java, ele rapidamente desiste das arrays para trabalhar com a ArrayList, que encapsula algumas rotinas comuns e trabalhosas. Depois o programador começa a se preocupar mais com o encapsulamento e passa a se refernciar as ArrayLists como List.

Um último passo que é mais díficil do programador tomar é de abstrair suas referências para Collection. Isso talvez ocorra por causa da óbvia ausência do método get(int) na interface Collection, apesar de que em muitos casos estamos apenas usando uma coleção para guardar um punhado de referências, sem nos importar com a ordem em que vamos percorrê-las e nem vamos precisar pegar uma referência que está em determinada posição (acesso aleatório). É nesse caso que a utilização dos Sets (em especial os que usam tabelas de espalhamento) é fortemente indicada (e mesmo que você precisasse garantir a ordem para iterar as referências, poderia usar uma coleção como a LinkedHashSet).

No curso de Java e Orientação a Objetos da Caelum, temos alguns exercícios extras. No capítulo de coleções tentamos mostrar a grande diferença de se usar um HashSet em vez de uma ArrayList quando precisamos realizar muitas buscas nessa coleção (isto é, usar o contains()). Você pode baixar essa apostila através do site.

Segue um código em que inserimos 30 mil referências a objetos do tipo Integer em uma ArrayList, e depois fazemos uma busca por cada um deles. Repare que 30 mil não é um número incomum.

public class TestaCollections {
  public static void main(String[] args) {
    Collection<Integer> colecao = new ArrayList<Integer>();

    long tempoInicial = System.currentTimeMillis();

    for (int i = 0; i < 30000; i++) {
      colecao.add(i);
    }

    for (int i = 0; i < 30000; i++) {
      colecao.contains(i);
    }

    long tempoFinal = System.currentTimeMillis();
    System.out.printf("%.3f ms%n"(tempoFinal - tempoInicial1000d);
  }
}

O resultado: 8,891 ms.

Vamos agora realizar o mesmo teste só que com uma estrutura que utilize tabelas de espalhamento, nesse caso o HashSet. Mudamos a seguinte linha:

    Collection<Integer> colecao = new ArrayList<Integer>();

Para:

    Collection<Integer> colecao = new HashSet<Integer>();

O novo resultado? Incríveis 0,078 ms. 114 vezes mais rápido! E se você aumentar o tamanho dos laços dos testes, essa proporção só vai aumentar! Porque isso? O contains da ArrayList faz uma busca linear, já o HashSet utiliza uma tabela de espalhamento para tentar fazer a busca em tempo constante.

Você ainda pode fazer outros testes interessantes. Um TreeSet será mais devagar para inserir, pois mantém uma árvore rubro negra como estrutura de dados interna, e sua busca muito melhor que a da ArrayList e um pouco mais lenta que a do HashSet, porém teremos nossos elementos ordenados. Comparar a inserção e busca de elementos entre LinkedList e ArrayList também mostra números impressionantes: percorrer uma LinkedList sem usar um Iterator (ou enhanced for) é absurdamente lento, já que o seu método get(int) percorre a lista toda desde o começo, por sua vez, na ArrayList inserir elementos nas posições iniciais não é nada performático (é necessário deslocar todo o restante da array pra frente).

Obviamente uma boa função de hash também é essencial. Recentemente a Sun melhorou a função de hash dos nomes dos métodos que o compilador utilizava para depois procurá-los, e com apenas isso conseguiu uma melhoria de performance de 2% na execução geral. Imagine se eles utilizassem uma List para isso? Dominar as coleções e estruturas de dados do Java é fundamental para um bom código.