Conteúdo


Pare de Escrever Tanto Código!

Aprenda os benefícios de reutilização de código com estas quatro classes Apache Commons Lang

Comments

Antes de Iniciar

Sobre este Tutorial

Commons Lang é um componente do Apache Commons, um projeto macro cujos muitos subprojetos estão relacionados a diversos aspectos de desenvolvimento de software na linguagem Java™ . Commons Lang estende a API java.lang padrão com métodos de manipulação de string, métodos numéricos básicos, reflexo de objetos, criação e serialização e propriedades de System . Também contém um tipo enum herdável, suporte para diversos tipos de Exceptions aninhadas, aprimoramentos para java.util.Date e utilitários que ajudam a construir métodos, como hashCode, toString e equals. Considero Commons Lang útil em uma ampla variedade de verticais de aplicativos. Usando Commons Lang, você acabará escrevendo menos código, o que permite fornecer software pronto para a produção mais rapidamente e com mesmo defeitos. Este tutorial o orienta passo-a-passo pelos conceitos fundamentais de usar algumas poucas classes diferentes de Commons Lang e usar seu código de forma que você não tenha que escrever muito.

Objetivos

Você aprenderá como:

  • Implementar contratos de objetos, como equals e hashCode.
  • Verificar a funcionalidade adequada.
  • Implementar o método compareTo da interface Comparable .

Quando tiver concluído com esse tutorial, você entenderá os benefícios da biblioteca Commons Lang e aprenderá como escrever menos código.

Pré-requisitos

Para obter o máximo deste tutorial, você deve estar familiarizado com a sintaxe Java e os conceitos básicos do desenvolvimento orientado a objetos na plataforma Java. Você também deve estar familiarizado com refatoração e teste de unidade normal.

Requisitos do Sistema

Para seguir adiante e testar o código para este tutorial, você precisa:

A configuração recomendada do sistema para este tutorial é:

  • Um sistema que suporta o Sun JDK 1.5.0_09 (ou posterior) ou o IBM JDK 1.5.0 SR3 com pelo menos 500 MB de memória principal.
  • Pelo menos 20 MB de espaço em disco para instalar os componentes de software e os exemplos cobertos.

As instruções e os exemplos do tutorial são baseados em um sistema operacional Microsoft® Windows® . Todas as ferramentas cobertas no tutorial também funcionam nos sistemas Linux® e UNIX® .

Os Benefícios de Reutilização de Código

Nos tempos remotos de desenvolvimento de software, a produtividade de um desenvolvedor era considerada diretamente proporcional à quantidade de código que escrevia. Na época, parecia uma métrica plausível: o código produz essencialmente um ativo binário presumivelmente funcional, portanto, alguém que pareça estar escrevendo muito código deve estar trabalhando de forma diligente para produzir um aplicativo funcional. A métrica parece se aplicar a outros segmentos de mercado. Um contador que cuida de várias entregas de imposto de renda ou um barista que faz muitos espressos deve ser produtivo, certo? Ambos aparentemente geram mais receita para seus respectivos negócios, pois produzem muitos itens que devem produzir.

Mas nós aprendemos há algum tempo que mais linhas de código não estão relacionadas à produtividade. Muito código certamente indica atividade, mas atividade não está necessariamente correlacionada a progresso. Contadores que produzem muitas entregas de imposto de renda incorretas por dia são altamente ativos, mas estão produzindo pouco valor para seus cliente e empregadores. Um barista que serve café com a velocidade da luz, mas entrega os pedidos errados está com certeza muito ativo, mas não está sendo produtivo.

Mais Código Pode Significar Mais Defeitos

Felizmente, o segmento de mercado de software geralmente aceira que muito código pode ser uma coisa ruim. Dois estudos determinaram que o aplicativo médio contém de 20 a 250 erros por 1.000 linhas de código (consulte Recursos)! Essa métrica é conhecida como densidade de defeito. Você pode chegar a uma conclusão geral a partir desses dados: menos linhas de código significa menos defeitos.

É claro que ainda é necessário escrever código. Não estamos ainda no ponto em que aplicativos podem se escrever, mas estamos no ponto em que podemos pegar muito código emprestado. Ainda não realizamos reutilização em componentes de negócios — a visão de que um desenvolvedor para reutilizar o objeto Account de outro, por exemplo — mas a reutilização referente a plataforma já chegou. Uma proliferação de estruturas de software livre e código de suporte de fácil reutilização podem ajudá-lo a escrever um objeto Account (por exemplo) com o menor número de linhas possível.

Por exemplo, Hibernate e Spring são ubíquos na comunidade Java. Usando o exemplo do objeto Account , as equipes que estejam ingressando hoje em um projeto de desenvolvimento de área verde para construir um aplicativo de pedidos on-line (que requeira um objeto Account ) teriam uma enorme vantagem ao usar Hibernate ou uma estrutura object-relational mapping (ORM) concorrente que valesse a pena, em vez de escrever uma estrutura ORM desde o início. O mesmo é verdade para outros aspectos do aplicativo, como teste de unidade (você usuário algo como JUnit, certo?) ou injeção de dependência (Spring é um candidato óbvio). Isso é reutilização. Só é diferente do que acreditamos um dia que seria.

Pegando emprestadas ou reutilizando essas estruturas, você acaba tendo de escrever menos código e pode focar de forma mais apropriada no problema de negócios em questão. As próprias estruturas têm muito código, mas a questão é que você não precisa escrever nem manter o mesmo. Essa é a beleza de projetos bem-sucedidos de software livre: outras pessoas estão fazendo isso para você. E elas podem ser melhores nisso do que você.

Menos é Melhor

Menos linhas de código podem resultar em um tempo mais rápido para o mercado com menos defeitos. Mas a reutilização é importante não apenas por significar escrever menos código, mas também porque significar usar o que você poderia chamar de "sabedorias das multidões". Estruturas e ferramentas populares de software livre — como Hibernate, Spring, JUnit e o servidor da Web Apache — estão sendo usadas por muitas pessoas em todo o mundo em diversos aplicativos. Esse software endurecido pela batalha e testado não está livre de defeitos, mas você pode supor com segurança que quaisquer problemas que surgirem serão localizados e corrigidos rapidamente e sem nenhum custo.

O projeto Apache Commons já existe há anos e é estável. O release mais recente contém aproximadamente 90 classes e quase 1.800 testes de unidade. Apesar de informações de cobertura não serem publicadas (e certamente poder-se argumentar que esse projeto poderia ter baixa cobertura de código), os números falam por si só. Isso é essencialmente 20 testes por classe. Estou disposto a apostar que o código do projeto é tão bem testado quanto o seu.

Contratos de Objetos

A biblioteca Commons Lang é fornecida com um conjunto útil de classes, coletivamente conhecidas como construtores. Nesta seção, você aprenderá como usar um deles para construir o método java.lang.Object equals e ajudar a reduzir a quantidade de código que você escreve.

Desafios da Implementação do Método

Todas as classes Java herdam implicitamente de java.lang.Object. E como você provavelmente já sabe, a classe Object possui três métodos que geralmente devem ser substituídos:

  • equals
  • hashCode
  • toString

Os métodos equals e hashCode são especiais porque outros aspectos da plataforma Java, como coletas e até mesmo estruturas de persistência (inclusive Hibernate), dependem desses dois métodos para que sejam implementados corretamente.

Se você nunca tiver implementado equals e hashCode, pode supor que isso seja uma tarefa simples — mas você estaria errado. O livro Java Efetivo de Joshua Bloch (consulte Recursos) devota mais de 10 páginas aos detalhes da implementação do método equals . Se você acabar implementando o método equals , será necessário implementar o método hashCode também (pois o contrato do equals determina que dois objetos iguais devem ter códigos hash idênticos). Bloch usa mais 6 páginas explicando o método hashCode . Isso é pelo menos 16 páginas de informações detalhadas sobre como implementar de forma apropriada 2 métodos aparentemente simples.

O desafio de implementar o método equals é com o contrato de que esse método deve seguir. equals deve:

  • Ser reflexivo:
    • Para algum objeto, foo (que não é null), foo.equals(foo) deve retornar true.
  • Ser simétrico:
    • Para objetos foo e bar (que não são null), se foo.equals(bar) retornar true, então, bar.equals(foo) também deve retornar true.
  • Ser transitivo:
    • Para objetos foo, bar e baz (que não são null), se foo.equals(bar) for true e bar.equals(baz) for igual a true, então, foo.equals(baz) também deve retornar true.
  • Ser consistente:
    • Para objetos foo e barse foo.equals(bar) retornar true, então, o método equals deve sempre retornar true independentemente de quantas vezes o método equals for chamado (desde que nenhum objetos realmente seja alterado).
  • Tratar nulls corretamente:
    • foo.equals(null) deve retornar false.

Após ler isso e, até mesmo, possivelmente estudar Java Efetivo, você pode achar que está apto para o desafio de implementar corretamente o método equals em seu objeto Account . Mas lembre-se do ponto que mencionei anteriormente referente à produtividade e à atividade.

Você deve estar construindo um aplicativo da Web on-line para seus negócios e quanto antes esse aplicativo estiver ativo, mais cedo você e seus negócios podem fazer dinheiro. Armado com esse simples fato, você gasta algumas horas (ou dias?) implementando e testando corretamente o contrato de equals em seus objetos — ou faz sentido reutilizar o código de outra pessoa?

Construindo equals

Para a implementação do método equals , EqualsBuilder de Commons Lang é útil. Esse classe é simples de entender. Possui essencialmente dois métodos que você precisa conhecer: append e isEquals. O método append aceita duas propriedades: uma do objeto subjacente e a mesma do objeto que está sendo comparado. Como o método append retorna uma instância do EqualsBuilder, você pode encadear chamadas sucessivas e comparar todas as propriedades desejadas de um objeto. E você pode concluir a string chamando o método isEquals .

Por exemplo, crie um objeto Account como fiz na Listagem 1:

Listagem 1. Um Objeto Account Simples
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

import java.util.Date;

public class Account implements Comparable {
 private long id;
 private String firstName;
 private String lastName;
 private String emailAddress;
 private Date creationDate;

 public Account(long id, String firstName, String lastName,
   String emailAddress, Date creationDate) {
  this.id = id;
  this.firstName = firstName;
  this.lastName = lastName;
  this.emailAddress = emailAddress;
  this.creationDate = creationDate;
 }

 public long getId() {
  return id;
 }

 public String getFirstName() {
  return firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public String getEmailAddress() {
  return emailAddress;
 }

 public Date getCreationDate() {
  return creationDate;
 }
}

O objeto Account , para nossos propósitos, é bem simples e isolado. Neste ponto, você pode executar um teste rápido, conforme mostrado na Listagem 2, para ver se você pode depender da implementação padrão de equals:

Listagem 2. Testando o Método equals Padrão do Objeto Account
import org.junit.Test;
import org.junit.Assert;
import com.acme.app.Person;

import java.util.Date;

public class AccountTest {
 @Test
 public void verifyEquals(){
  Date now = new Date();
  Account acct1 = new Account(1, "Andrew", "Glover", "ajg@me.com", now);
  Account acct2 = new Account(1, "Andrew", "Glover", "ajg@me.com", now);

  Assert.assertTrue(acct1.equals(acct2));
 }
}

Como pode ver na Listagem 2, criei dois objetos Account idênticos, cada um com sua própria referência (assim == retornaria false). Quando tento ver se são iguais, JUnit informa que false é retornado em vez disso.

Lembre-se de que o método equals é usado por diversos aspectos da plataforma Java, incluindo as classes de coleta da linguagem Java. Portanto, faz muito sentido implementar uma versão funcional desse método. Consequentemente, irei substituir o método equals .

Lembre-se de que o contrato de equals não pode funcionar com objetos null . Além disso, dois tipos diferentes de objetos (Accounts e Persons, por exemplo) não podem ser iguais. Por fim, no código Java, o método equals é distintamente diferente do operador == (que, se você se lembrar, retorna true se dois objetos compartilharem a mesma referência; consequentemente, esses dois objetos devem ser iguais). Dois objetos poderiam ser iguais (e, assim, retornarem true para equals), mas não compartilharem a mesma referência.

Consequentemente, o primeiro aspecto de um método equals pode ser codificado como na Listagem 3:

Listagem 3. Condicionais Rápidos em equals
if (this == obj) {
 return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
 return false;
}

Na Listagem 3, crio dois condicionais que devem ser verificados antes de eu tentar comparar as respectivas propriedades do objetos subjacente e do parâmetro obj passado.

Em seguida, como o método equals aceita um tipo Object , faz sentido efetuar cast do parâmetro obj para Account, conforme mostrado na Listagem 4:

Listagem 4. Efetuando Cast do Parâmetro obj
Account account = (Account) obj;

Supondo que a lógica de equals chegou até aqui, está a hora de usar o objeto EqualsBuilder . Lembre-se, esse objeto foi projetado para comparar propriedades semelhantes do objeto subjacente (this) ao tipo passado no método equals , usando o método append . Como esses métodos podem ser encadeados, é possível concluir a string com o método isEquals , que retorna true ou false. Consequentemente, você pode escrever essencialmente uma linha de código, como fiz na Listagem 5:

Listagem 5. Reutilizando EqualsBuilder
return new EqualsBuilder().append(this.id, account.id)
  .append(this.firstName, account.firstName)
   .append(this.lastName, account.lastName)
    .append(this.emailAddress, account.emailAddress)
     .append(this.creationDate, account.creationDate)
      .isEquals();

Juntar tudo produz um método equals como o da Listagem 6:

Listagem 6. equals Totalmente Codificado
public boolean equals(Object obj) {
 if (this == obj) {
  return true;
 }
 if (obj == null || this.getClass() != obj.getClass()) {
  return false;
 }

 Account account = (Account) obj;

 return new EqualsBuilder().append(this.id, account.id)
  .append(this.firstName, account.firstName)
   .append(this.lastName, account.lastName)
    .append(this.emailAddress, account.emailAddress)
     .append(this.creationDate, account.creationDate)
      .isEquals();
}

Agora, execute novamente o teste anteriormente em falha (consulte a Listagem 2). Você deve ver um resultado poderoso.

Você não gastou nenhum tempo tentando escrever sua própria versão de equals. Se ainda estiver curioso sobre como escrever um método equals apropriado, é suficiente dizer que envolve muitos condicionais. Por exemplo, um pequeno trecho de código de um método equals não implementado por EqualsBuilder poderia comparar a propriedade creationDate , conforme mostrado na Listagem 7:

Listagem 7. Um Trecho de Código de seu Próprio Método equals
if (creationDate != null ? !creationDate.equals(
   person.creationDate) : person.creationDate != null){
 return false;
}

Observe que usar um ternário, nesse caso, torna o código um pouco mais preciso, mas discutivelmente ao custo da compreensão. No entanto, o ponto é que eu poderia escrever uma série de condicionais comparando os diversos aspectos das propriedades de cada objeto ou poderia usar EqualsBuilder (que faz exatamente a mesma coisa). Qual você escolheria?

Observe também que se você realmente quiser simplificar seu método equals e escrever o mínimo de código possível (o que significa menos código para manter), você pode usar o poder do reflexo e escrever o código da Listagem 8:

Listagem 8. Usando a API de Reflexo de EqualsBuilder
public boolean equals(Object obj) {
 return EqualsBuilder.reflectionEquals(this, obj);
}

Que tal para uma redução de código?

A Listagem 8 tem um lado negativo. O EqualsBuilder deve desativar furtivamente o controle de acesso no objeto subjacente (para comparar os campos private ). Isso pode falhar se sua VM for configurada com segurança em mente. E o reflexo pesado usado pela Listagem 8 pode afetar o desempenho do tempo de execução do método equals . Do lado positivo, no entanto, se você incluir novas propriedades e estiver usando a API de reflexo, não será necessário atualizar seu método equals (como você faria se tivesse mantido a maneira sem reflexo).

Com EqualsBuilder, você pode usar o poder de reutilização. Fornece duas opções para implementar o método equals . O que você escolhe depende de você e de sua situação específica. O estilo de uma linha é simples e fácil, mas, como você agora entende, não sem riscos.

Usando Hash em Objetos

Agora que você superou um método equals apropriado sem precisar gravar muito código, não pode esquecer de substituir hashCode também. Esta seção mostra como fazer isso.

Construindo hashCode

O método hashCode também possui um contrato, mas não é tão formal como o contrato equals . No entanto, é importante que entenda o mesmo corretamente. Como com o equals, o resultado deve ser consistente. E se dois objetos, foo e bar, retornarem true para foo.equals(bar), então, ambos os métodos hashCode de foo e bar devem retornar o mesmo valor. Se foo e bar não forem iguais, não precisam retornar diferentes códigos hash; no entanto, os Javadocs sugerem que se esses objetos não tiverem resultados diferentes, as coisas geralmente operarão de uma melhor forma.

Vale a pena observar também que, como você provavelmente observou antes, hashCode retorna o que é aparentemente um número inteiro aleatório mesmo se você não substituí-lo. Isso porque a plataforma subjacente geralmente converte o local do endereço do objeto subjacente em um número inteiro; independentemente disso, a documentação indica que isso não é um requisito e, assim, poderia ser alterado. Independentemente disso, se você acabar substituindo o método equals , faz sentido substituir o método hashCode . (Lembre-se de que Java Efetivo de Joshua Bloch usa seus páginas sobre como implementar corretamente o método hashCode mesmo parecendo que funciona como é fornecido.)

A biblioteca Commons Lang fornece uma classe HashCodeBuilder que opera de forma quase idêntica ao EqualsBuilder. Mas em vez de comparar duas propriedades, anexa uma única propriedade para gerar um número inteiro que obedeça o contrato que acabo de descrever.

Em seu objeto Account , siga em frente e substitua o método hashCode , conforme mostrado na Listagem 9:

Listagem 9. Um Método hashCode
public int hashCode() {
 return 0;
}

Como não há nada a comparar quando você gera um código hash, usar HashCodeBuilder é em uma linha só. O que é importante é inicializar o HashCodeBuilder corretamente. O construtor aceita dois ints, que usa para criar um código hash. Esses dois ints devem ser ímpares. O método append aceita uma propriedade e, como antes, esses métodos podem ser encadeados. A string pode ser concluída com uma chamada ao método toHashCode .

Considerando essas informações, você pode, então, implementar um método hashCode como fiz na Listagem 10:

Listagem 10. Implementando um Método hashCode com HashCodeBuilder
public int hashCode() {
 return new HashCodeBuilder(11, 21).append(this.id)
  .append(this.firstName)
   .append(this.lastName)
    .append(this.emailAddress)
     .append(this.creationDate)
      .toHashCode();
}

Observe que passo um 11 e um 21 no construtor. Esses são números ímpares escolhidos completamente de forma aleatória para esse objeto. Abra AccountTest anterior (consulte Listagem 2). Inclua uma verificação rápida para verificar o contrato que se equals retornar true para dois objetos, então, hashCode deve retornar o mesmo número. A Listagem 11 mostra o teste modificado:

Listagem 11. Verificando o Contrato de hashCode para Dois Objetos Iguais
import org.junit.Test;
import org.junit.Assert;
import com.acme.app.Account;

import java.util.Date;

public class AccountTest {
 @Test
 public void verifyAccountEquals(){
  Date now = new Date();
  Account acct1 = new Account(1, "Andrew", "Glover", "ajg@me.com", now);
  Account acct2 = new Account(1, "Andrew", "Glover", "ajg@me.com", now);

  Assert.assertTrue(acct1.equals(acct2));
  Assert.assertEquals(acct1.hashCode(), acct2.hashCode());
 }
}

Na Listagem 11, verifique que dois objetos iguais compartilham o mesmo código hash. Em seguida, na Listagem 12, também verifico que dois objetos diferentes têm códigos hash diferentes:

Listagem 12. Verificando o Contrato de hashCode para Dois Objetos Diferentes
@Test
public void verifyAccountDifferentHashCodes(){
 Date now = new Date();
 Account acct1 = new Account(1, "John", "Smith", "john@smith.com", now);
 Account acct2 = new Account(2, "Andrew", "Glover", "ajg@me.com", now);

 Assert.assertFalse(acct1.equals(acct2));
 Assert.assertTrue(acct1.hashCode() != acct2.hashCode());
}

Se, apenas por curiosidade, você mesmo quisesse codificar um método hashCode , como o faria? Lembrando-se do contrato de hashCode , você poderia codificar algo como a Listagem 13:

Listagem 13. Implementando seu Próprio hashCode
public int hashCode() {
 int result;
 result = (int) (id ^ (id >>> 32));
 result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
 result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
 result = 31 * result + (emailAddress != null ? emailAddress.hashCode() : 0);
 result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0);
 return result;
}

Sem precisar dizer, esse código funciona como um método hashCode válido, mas qual dos dois métodos hashCode você preferiria manter? Qual você pode compreender mais rapidamente? Observe como novamente as instruções ternárias são usadas na Listagem 13 para evitar muita lógica condicional. Você pode imaginar que HashCodeBuilder de Commons Lang está provavelmente fazendo algo semelhante — mas a beleza é que os desenvolvedores de Commons Lang estão fazendo a manutenção e os testes.

Como o EqualsBuilder, o HashCodeBuilder possui outra API que usa reflexo. Com ela, você não precisa incluir cada propriedade do objeto subjacente manualmente com um método append , resultando em um método hashCode como o da Listagem 14:

Listagem 14. Usando a API de Reflexo de HashCodeBuilder
public int hashCode() {
 return HashCodeBuilder.reflectionHashCode(this);
}

Como antes, como esse método se aplica ao reflexo Java por baixo do pano, o ajuste da segurança pode interromper a funcionalidade e o desempenho pode ser um pouco mais lento.

Comparativamente Comparável

Outro método interessante que sugere um contrato um tanto quanto formal é o método compareTo da interface Comparable . Essa interface acaba sendo bem importante caso você precise de controle específico sobre como objetos específicos são ordenados. Nesta seção, você aprenderá como usar CompareToBuilder de Commons Lang.

Ordenando a Saída

Como você provavelmente já observou em suas tentativas de programação Java no passado, há mecanismos padrão para como os objetos são ordenados em determinados eventos, como o método sort da classe Collections .

Por exemplo, Collection na Listagem 15 está fora de ordem e permanecerá dessa forma se você não fizer nada:

Listagem 15. Uma Lista de Strings
ArrayList<String> list = new ArrayList<String>();
list.add("Megan");
list.add("Zeek");
list.add("Andy");
list.add("Michelle");

Ainda assim, se você passar list para o método sort' de Collections , como faço na Listagem 16, a ordenação padrão será aplicada, que, neste caso, é alfabética. A Listagem 16 classifica e imprime uma lista ordenada alfabeticamente dos nomes na Listagem 15:

Listagem 16. Classificando uma Lista de Strings
Collections.sort(list);

for(String value : list){
 System.out.println("sorted is " + value);
}

A Listagem 17 mostra a saída:

Listagem 17. Uma Saída Classificada de Strings
sorted is Andy
sorted is Megan
sorted is Michelle
sorted is Zeek

A razão para isso funcionar, é claro, é que a classe Java String implementa a interface Comparable e, assim, tem uma implementação do método compareTo que permite a classificação alfabética. Na verdade, praticamente todas as principais classes na linguagem Java implementam essa interface.

E se você quisesse permitir uma coleta de Accounts para serem ordenadas de diversas maneiras — por exemplo, através de id ou sobrenome? Como poderia atingir essa meta?

É claro que primeiro teria de implementar a interface Comparable e, em seguida, implementar o método compareTo . Esse método pode ser usado essencialmente somente para ordenação natural — assim, um objeto é ordenado de acordo com suas propriedades. Consequentemente, compareTo é bem semelhante ao método equals , ainda assim, permite que uma coleta de Accounts seja classificada por suas propriedades na ordem em que as propriedades são processadas através do método compareTo .

Se você ler a documentação para implementar esse método, descobrirá que é bem semelhante à de equals; ou seja, fazer corretamente pode ser difícil. (Java Efetivo devota quatro páginas ao assunto.) Agora, sem dúvida, você já entendeu o padrão: você irá apenas usar Commons Lang para fazer corretamente.

Construindo compareTo

Commons Lang fornece um CompareToBuilder denominado de forma habilidosa que funciona praticamente de forma idêntica ao EqualsBuilder. Inclui um método append encadeável e você pode retornar, consequentemente, um int através do método toComparison .

Assim, para iniciar o jogo, você deve primeiro alterar a classe Account para implementar a interface Comparable , conforma mostrado na Listagem 18:

Listagem 18. Implementando a Interface Comparable
public class Account implements Comparable {}

Em seguida, você deve implementar o método compareTo , conforme mostrado na Listagem 19:

Listagem 19. A Implementação Padrão de compareTo
public int compareTo(Object obj) {
 return 0;
}

Implementar esse método é um processo em duas etapas. Primeiro, você deve efetuar cast do tipo de parâmetro recebido para seu tipo desejado (Account neste caso). Em seguida, você usa CompareToBuilder para comparar as propriedades de seu objeto. A documentação de Commons Lang determina que você deve comparar as mesmas propriedades comparadas no método equals ; assim, o método compareTo do objeto Account deve ter a aparência da Listagem 20:

Listagem 20. Usando o CompareToBuilder
public int compareTo(Object obj) {
 Account account = (Account) obj;
 return new CompareToBuilder().append(this.id, account.id)
  .append(this.firstName, account.firstName)
   .append(this.lastName, account.lastName)
    .append(this.emailAddress, account.emailAddress)
     .append(this.creationDate, account.creationDate)
      .toComparison();
}

Não se esqueça, se você realmente quiser reduzir sua codificação, você sempre pode usar a API com estilo de reflexo CompareToBuilder , como fiz na Listagem 21:

Listagem 21. Usando a API de Reflexo de CompareToBuilder
public int compareTo(Object obj) {
 return CompareToBuilder.reflectionCompare(this, obj);
}

Agora, se você precisar depender, por exemplo, da ordenação padrão para uma coleta de Accounts, pode usar Collections.sort, conforme mostrado na Listagem 22:

Listagem 22. Classificando uma Lista Comparável de Accounts
Date now = new Date();
ArrayList<Account> list = new ArrayList<Account>();
list.add(new Account(41, "Amy", "Glover", "ajg@me.com", now));
list.add(new Account(10, "Andrew", "Glover", "ajg@me.com", now));
list.add(new Account(1, "Andrew", "Blover", "ajg@me.com", new Date()));
list.add(new Account(2, "Andrew", "Smith", "b@bb.com", now));
list.add(new Account(0, "Andrew", "Glover", "z@zell.com", new Date()));

Collections.sort(list);

for(Account acct : list){
 System.out.println(acct);
}

Esse código emite a saída dos objetos em ordem natural de acordo com id, em seguida, o nome, depois, o sobrenome, etc. Consequentemente, a ordem classificada seria o que está mostrado na Listagem 23:

Listagem 23. Uma Lista Classificada de Accounts
new Account(0, "Andrew", "Glover", "z@zell.com", new Date())
new Account(1, "Andrew", "Blover", "ajg@me.com", new Date())
new Account(2, "Andrew", "Smith", "b@bb.com", now)
new Account(10, "Andrew", "Glover", "ajg@me.com", now)
new Account(41, "Amy", "Glover", "ajg@me.com", now)

Fazer sentido dessa saída é outra questão. Na próxima seção, você verá como Commons Lang pode ajudá-lo a construir resultados mais legíveis.

Representações de String de Objetos

A implementação padrão do método toString de Object retorna o nome completo do objeto seguido por um caractere @ e, em seguida, pelo valor do código hash do objeto. E como você provavelmente descobriu há muito tempo, isso não é muito útil para distinguir os objetos uns dos outros. Commons Lang possui uma classe útil ToStringBuilder que ajuda a construir um resultado mais legível toString .

Construindo toString

Você provavelmente codificou um método toString em mais de uma ocasião — eu sei que eu fiz isso. Esses métodos não são complicados e é difícil codificá-los incorretamente; no entanto, eles poderiam ser considerados como inconvenientes. E como seu objeto Account já depende da biblioteca Commons Lang, vamos dar uma olhada e ver o ToStringBuilder em ação.

O ToStringBuilder age da mesma maneira que outras três classes que cobri. Você cria uma instância da mesma, anexa algumas propriedades e chama toString. É isso.

Siga em frente e substitua o método toString e inclua o código da Listagem 24:

Listagem 24. Usando o ToStringBuilder
public String toString() {
 return new ToStringBuilder(this).append("id", this.id).
  .append("firstName", this.firstName)
   .append("lastName", this.lastName)
    .append("emailAddress", this.emailAddress)
     .append("creationDate", this.creationDate)
      .toString();
}

Como sempre, você também pode usar reflexo, conforme mostrado na Listagem 25:

Listagem 25. Usando a API de Reflexo de ToStringBuilder
public String toString() {
 return ToStringBuilder.reflectionToString(this);
}

Independentemente de como você optar por usar ToStringBuilder, chamar toString resulta em uma produção mais legível de String. Por exemplo, considere a instância do objeto na Listagem 26:

Listagem 26. Uma Instância Exclusiva de Account
new Account(10, "Andrew", "Glover", "ajg@me.com", now);

Como pode ver na Listagem 27, a saída é bem legível:

Listagem 27. Saída de ToStringBuilder
com.acme.app.Account@47858e[
   id=10,firstName=Andrew,lastName=Glover,emailAddress=ajg@me.com,
   creationDate=Tue Nov 11 17:20:08 EST 2008]

Se não estiver feliz com essa representação específica de String de seu objeto, a biblioteca Commons Lang possui algumas classes auxiliares que podem ajudar na saída customizada. No mínimo, usar ToStringBuilder possibilita uma representação consistente das instâncias dos objetos em arquivos de log, por exemplo.

Menos é Mais

Felizmente, nas últimas duas décadas aproximadamente, o segmento de mercado de software começou a aprender que muito código pode ser uma coisa ruim. Os estudos que citei deixa-nos supor que menos linhas de código significa menos defeitos.

A enorme proliferação de software livre significa que a reutilização de código já está sendo realizada. Apesar de ainda estarmos esperando pela chegada do dia da verdadeira reutilização de componente, agora temos estruturas e código de suporte de fácil utilização, que permitem que você escreva aplicativos com o menor número de linha de código possível.

Então, o que você está aguardando? Vá em frente e comece a usar o projeto Apache Commons Lang. Enquanto estiver fazendo isso, explore o que mais há nessa útil biblioteca. Você descobrirá que o tempo e os pressionamentos de teclas que você economizará valem muito o esforço.


Recursos para download


Temas relacionados

  • Commons Lang: Commons Lang fornece um host de utilitários auxiliares para a API java.lang .
  • Sun JDK 1.5 ou posterior: Você precisará de pelo menos a versão 1.5.0_09 para seguir os exemplos deste tutorial.
  • In pursuit of code quality (Andrew Glover, developerWorks): Esta série explora técnicas, ferramentas e métodos para assegurar e medir qualidade de software.
  • Effective Java: Programming Language Guide (Joshua Bloch, Prentice Hall, 2001): O livro de Bloch cobre os detalhes de implementações do método java.lang.Object .
  • "Hashing it out" (Brian Goetz, developerWorks, maio de 2003): Esta parte da série Teoria e Prática de Java cobre regras e diretrizes para definir hashCode e equals de forma efetiva e apropriada.
  • "Why Do CMMI Assessments?" (Donna Dunaway e Marilyn Bush, InformIt, junho de 2005): Um capítulo de amostra de Avaliações de CMMI® : Motivando Mudança Positiva (Addison Wesley, 2005), que descreve alguns estudos de densidade de defeito.
  • "Linux: Fewer Bugs Than Rivals" (Michelle Delio, Wired, dezembro de 2004): Outro artigo que descreve algumas métricas referentes à densidade do defeito.
  • Commons Lang: Faça download de Commons Lang.

Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=387313
ArticleTitle=Pare de Escrever Tanto Código!
publish-date=12162008