Programação Web em Java
Autor: Kayo Bruno | Publicado: 2012-02-11T17:54:00-08:00

Sejam bem vindos, esta é a décima segunda aula de Programação Web em Java.

O conteúdo de Java está no final, logo iremos começar nossa primeira aplicação Web com JSP.

Hoje iremos aprender alguns conceitos e técnicas para trabalhar com coleções.

Décima Segunda Aula: &nbsp;Coleções, Hashing e Comparadores.

Hashing

Hashing é uma maneira de identificar objetos com um número inteiro. Coisas iguais tem (ou deveriam ter) o mesmo número hash. Isso é extremamente útil para realização de comparações mais rápidas.

Em classes escritas por você mesmo, para isso funcionar corretamente, você deve sobrescrever a função .hashCode() e gerar seu próprio número hash.

A função .HashCode()

Esta função deve retornar um número inteiro. É definida na classe java.lang.Object e retorna altomaticamente um valor único para cada instância (para cada objeto).

Se sua classe tem comportamento diferente deste, você deve sobrescrever esta função.

As três premissas da função .HashCode() são:

O hashCode de um objeto não deve mudar se o valor de um objeto não mudar.
Dois objetos iguais DEVEM ter o mesmo hashCode
É desejável que dois objetos diferentes tenham hashCode distintos (sempre que isso for possível)

Exemplos de hashCode:

String scott = "Scotty";
String scott2 = "Scotty";
String corey = "Corey";
System.out.println(scott.hashCode());
System.out.println(scott2.hashCode());
System.out.println(corey.hashCode()); &nbsp;// =&gt; -1823897190, -1823897190, 65295514
Integer int1 = 123456789;
Integer int2 = 123456789;
System.out.println(int1.hashCode());
System.out.println(int2.hashCode()); // =&gt; 123456789, 123456789

Classe NomeCompleto

Para prosseguir iremos definir uma classe chamada NomeCompleto, que será utilizada nos exemplos do restante da aula:

public class NomeCompleto {
&nbsp; public String nome;
&nbsp; public String sobrenome;
&nbsp; public NomeCompleto(String nome, String sobrenome) {
&nbsp; &nbsp; this.nome = nome;
&nbsp; &nbsp; this.sobrenome = sobrenome;
&nbsp; }
&nbsp; public String toString() {
&nbsp; &nbsp; return nome + " " + sobrenome;
&nbsp; }
&nbsp; public boolean equals(Object o) {
&nbsp; &nbsp; return (o instanceof NomeCompleto &amp;&amp;((NomeCompleto) o).nome.equals(this.nome) &amp;&amp;((NomeCompleto) o).sobrenome.equals(this.sobrenome));
&nbsp; }
}

Vamos verificar se esta classe NomeCompleto está funcionando corretamente:

NomeCompleto maria = new NomeCompleto("Maria", "Lúcia");
NomeCompleto jose = new NomeCompleto("José", "Silva");
NomeCompleto jose2 = new NomeCompleto("José", "Silva");
System.out.println(maria.equals(jose));
System.out.println(jose.equals(jose2));
System.out.println(maria.hashCode());
System.out.println(jose.hashCode());
System.out.println(jose2.hashCode()); // ⇒ false, true, 6718604, 7122755, 14718739

Objetos são iguais, mas o hashCode não é. Precisamos arrumar esta classe.

Você não pretende usar a classe hashCode, portanto não precisa se preocupar com isso não é? ERRADO! Você precisa resolver isso porque irá ter problemas com listas e coleções. As classes que implementam as listas pressupõem que a função hashCode() funciona corretamente.

Aliás, estamos vendo hashCode no início desta aula, justamente para podermos usar listas na continuação, e lembrá-lo que antes de usar listas deve sobrescrever esta função.

O que deve fazer a função hashCode mesmo? Gerar um número inteiro único para objetos com conteúdo iguais, e diferentes para objetos de conteúdo diferente.

Possível implementação para o nosso exemplo:

public class NomeCompleto {
&nbsp; ...
&nbsp; public int hashCode() {
&nbsp; &nbsp; return nome.hashCode() *23 + sobrenome.hashCode();
&nbsp; }
}

Veja que as classes String já implementam hashCode corretamente. Usamos “*23″ para que José Maria seja diferente de Maria José. Faça testes e verifique que sem a multiplicação o hashCode gerado é o mesmo.

Tenha certeza que entendeu este tópico antes de prosseguir. Funções hashCode não implementadas ou com código errado irão provocar problemas estranhos no uso de coleções. Estes problemas poderão te deixar realmente confuso. Se tiver dúvidas nesta parte pergunte &nbsp;nos comentários antes de continuar.

Coleções

Coleções são implementações da biblioteca java para executar:

Coleções de objetos
Ordenação de objetos
Estocagem de objetos
Recuperação e busca de objetos estocados
Para usar Coleções deve-se incluir a importação de java.util.* no topo de cada arquivo *.java:

package lab2;
import java.util.*;
public class UsandoColecoes {
&nbsp; List&lt;String&gt; lista = new ArrayList&lt;String&gt;();
&nbsp; ... //resto da classe
}

Sintaxe básica de uma coleção de NomeCompleto:

Adiciona um nome a lista: boolean add(NomeCompleto o);
Verifica se um objeto está na lista: boolean contains(Object o);
Remove um elemento da lista: boolean remove(NomeCompleto o);
Verifica quantos elementos tem na lista: int size();


Exemplo de uso:


List&lt;NomeCompleto&gt; iapjava = new ArrayList&lt;NomeCompleto&gt;();
iapjava.add(new NomeCompleto("Luis", "Lima");
iapjava.add(new NomeCompleto("José", "Silva");
System.out.println(iapjava.size()); // =&gt; 2
iapjava.remove(new NomeCompleto("José", "Silva");
System.out.println(iapjava.size()); &nbsp;// =&gt; 1
List&lt;NomeCompleto&gt; iapruby = new ArrayList&lt;NomeCompleto&gt;();
Iapruby.add(new NomeCompleto("Marcos", "Fontes"));
iapjava.addAll(iapruby);
System.out.println(iapjava.size()); // =&gt; 2

Coleções Genéricas

É permitido definir uma coleção que só armazene objetos de um determinado tipo como no exemplo acima. Porém também é permitido definirmos coleções genéricas que armazenam objetos de qualquer tipo.
Exemplo de coleções genéricas:

List untyped = new ArrayList();
List&lt;String&gt; typed = new ArrayList&lt;String&gt;();
Object obj = untyped.get(0);
String sillyString = (String) obj;
String smartString = typed.get(0);

As coleções não genéricas ou tipadas são mais práticas de manipular.

Recuperando todos os dados de uma lista:

Dado uma coleção de NomeCompleto chamada “colecaox” podemos usar duas ferramentas para recuperar cada item da coleção: Iterator e For each.

Iterator

Iterator&lt;NomeCompleto&gt; it = colecaox.iterator();
while (it.hasNext) {
&nbsp; &nbsp; &nbsp; &nbsp; NomeCompleto obj = it.next();
&nbsp; &nbsp; &nbsp; &nbsp; // Fazer alguma coisa com cada objeto
}
for (NomeCompleto obj : colecaox) {
&nbsp; &nbsp; &nbsp; &nbsp; // Fazer alguma coisa com cada objeto
}

Excluir objetos da lista enquanto se está recuperando os mesmo:

Não é permitido remover objetos de uma coleção enquanto a mesma está em um laço for-each. Para remover uma lista enquanto se está recuperando é necessário usar o Iterator:

Iterator&lt;NomeCompleto&gt; it = colecaox.iterator();
while (it.hasNext) {
&nbsp; &nbsp;NomeCompleto obj = it.next();
&nbsp; &nbsp;it.remove(obj); // Veja que só se remove pelo Iterator não pela coleção: colecaox.remove(Obj);
}

Tipos de Coleção disponíveis:


Listas:
ArrayList;
Conjunto: (set)
HashSet
TreeSet
Mapeamento (map)
HashMap
Listas

ArrayList é uma lista de objetos ordenados, similar a um Array, porém sem tamanho pré-definido. A lista é ordenada de acordo com a ordem de inserção.

Definindo e inserindo itens na lista:

List&lt;String&gt; strings = new ArrayList&lt;String&gt;();
strings.add("one");
strings.add("two");
strings.add("three");
// strings = [ "one", "two", "three"]

Inserindo objetos em uma determinada posição

List&lt;String&gt; strings = new ArrayList&lt;String&gt;();
strings.add("one");
strings.add("three");
strings.add(1, "two");
// strings = [ "one", "two", "three"]

Recuperando objetos de uma determinada posição:

s.o.print(strings.get(0)) &nbsp; // =&gt; "one"
s.o.print(strings.indexOf("one")) // =&gt; 0
Conjuntos (set)

Coleção sem tamanho e sem ordem definida. Não é permitido duplicidade de objetos no conjunto.

Exemplo:

Set&lt;NomeCompleto&gt; nomes = new HashSet&lt;NomeCompleto&gt;();
nomes.add(new NomeCompleto("José", "Silva"));
nomes.add(new NomeCompleto("José", "Silva"));
System.out.println(names.size()); =&gt; 1
Regra de ouro para Conjuntos (set):
Um elemento do conjunto não pode mudar se isso poderá afetá-la a ponto de torná-lo igual a um outro objeto do conjunto. (ou a outro objeto a ser inserido no conjunto mesmo após a mudança)

Se não respeitar esta regra, pode se preparar para coisas realmente estranhas. De preferência sempre use objetos imutáveis com conjuntos.

Mapas (map)

Mapas são conjuntos de pares Chave/Valor. Para cada chave o mapa armazena um valor correspondente. Com o valor da chave a coleção map pode recuperar o valor previamente armazenado. Uma chave deve ser única, porém valores podem repetir.

É um serviço muito útil para armazenamento e posterior recuperação de dados com valor chave. Utilíssimo para uso com banco de dados relacionais. Iremos usar isso muito, então não deixe dúvidas a respeito

Mapas não implementam as funções add e contais, em seu lugar temos as seguintes funções:


boolean put(Foo chave, Bar valor);
boolean contaisKey(Foo chave);
boolean contaisvalue(Bar value);
(neste exemplos Foo e Bar são duas classes de qualquer tipo)

Exemplo de uso:

Map&lt;String, String&gt; dns = new HashMap&lt;String, String&gt;();
dns.put("scotty.mit.edu", "18.227.0.87");
System.out.println(dns.get("scotty.mit.edu"));
System.out.println(dns.containsKey("scotty.mit.edu"));
System.out.println(dns.containsValue("18.227.0.87"));
dns.remove("scotty.mit.edu");
System.out.println(dns.containsValue("18.227.0.87"));
// =&gt; "18.227.0.87", true, true, false

Outros métodos úteis em mapas (map):

keySet() – retorna um Set de todos as chaves
values() – retorna uma Collection de todos os valores (use ArrayList ou Set para armazená-lo)
entrySet() – returna um Set de pares “chave,valor”. Cada par é um objeto Map.Entry. Map.Entry suporta getKey, getValue, setValue ;


Comparação e Ordenação

Para uso das coleções ordenadas é preciso também definirmos a função compareTo nas classes que definirmos. Esta função é usada para decidir, entre dois objetos, qual é o maior ou se são iguais.

(a.compareTo(b)) deverá resultar em um número menor que zero se a &lt; b, em zero se a = b e, finalmente, em um número maior que zero se a &gt; b .

Exemplos de comparação:

Integer um = 1;
System.out.println(um.compareTo(3));
System.out.println(um.compareTo(-50));
String jose= "José";
System.out.println(jose.compareTo("Ana"));
System.out.println(jose.compareTo("Zé"));
// =&gt; -1 , 1, 4, -2

Veja que as classes String e Integer já implementam uma função compareTo corretamente. Nas classes definidas pelo programador esta função deverá ser definida corretamente. Mais a frente iremos definí-la para nosso exemplo NomeCompleto. Por enquanto vamos ver como usar compareTo com listas para ordenar alfabeticamente uma lista:

List&lt;String&gt; nomes = new ArrayList&lt;String&gt;();
nomes.add("Sailor");
nomes.add("Lula");
nomes.add("Bobby");
nomes.add("Santos");
nomes.add("Dell");
Collections.sort(nomes);
// nomes =&gt; [ "Bobby", "Dell", "Lula", "Sailor", &nbsp;"Santos" ]

Interface Comparable

Podemos usar Colletions.sort com a lista acima porque Strings implementam Comparable. Isto é, Strings tem uma ordem natural. Para que uma classe NomeCompleto seja “Comparable”, você deve implementar:

int compareTo(NomeCompleto obj);

Exemplo:

public class NomeCompleto implements Comparable&lt;NomeCompleto&gt; {
&nbsp; ...
&nbsp; public int compareTo(NomeCompleto o) {
&nbsp; &nbsp; int compare = this.sobrenome.compareTo(o.sobrenome)
&nbsp; &nbsp; if (compare != 0)
&nbsp; &nbsp; &nbsp; return compare;
&nbsp; &nbsp; &nbsp; else return this.nome.compareTo(o.nome);
&nbsp; }
}

Usando a nova classe Comparable NomeCompleto:

List&lt;NomeCompleto&gt; nomes = new ArrayList&lt;NomeCompleto&gt;();
nomes.add(new NomeCompleto("Nicolas", "Cage"));
nomes.add(new NomeCompleto("Laura", "Dern"));
nomes.add(new NomeCompleto("Harry", "Stanton"));
nomes.add(new NomeCompleto("Diane", "Ladd"));
nomes.add(new NomeCompleto("William", "Morgan"));
nomes.add(new NomeCompleto("Dirty", "Glover"));
nomes.add(new NomeCompleto("Johnny", "Cage"));
nomes.add(new NomeCompleto("Metal", "Cage"));
System.out.println(nomes);
Collections.sort(nomes);
System.out.println(nomes);
// =&gt; [Johnny Cage, Metal Cage, Nicolas Cage, Laura Dern, Crispin Glover,
// Diane Ladd, William Morgan, Harry Stanton]

Objetos Comparadores (Comparator)

Para criar mais de um método de ordenação você deve definir classes Comparadoras. A classe comparadora simplesmente é uma classe com a implementação da função compateTo entre dois objetos:

int compare(Foo o1, Foo o2);

Exemplo de Comparador, ordenando primeiro nome primeiro:

public class PrimeiroNomePrimeiro implements
&nbsp; &nbsp; Comparator&lt;NomeCompleto&gt; {
&nbsp; &nbsp; public int compare(Nome n1, Nome n2) {
&nbsp; &nbsp; &nbsp; int ret = n1.nome.compareTo(n2.nome);
&nbsp; &nbsp; &nbsp; if (ret != 0)
&nbsp; &nbsp; &nbsp; &nbsp; return ret;
&nbsp; &nbsp; &nbsp; &nbsp; else return n1.sobrenome.compareTo(n2.sobrenome);
&nbsp; &nbsp; }
}

Isso deverá ser feito em arquivo separado do NomeCompleto.java: PrimeiroNomePrimeiro.java

Para usar o Comparador:

List&lt;NomeCompleto&gt; nomes = new ArrayList&lt;NomeCompleto&gt;();
...
Comparator&lt;NomeCompleto&gt; primeiroNome = new PrimeiroNomePrimeiro();
Collections.sort(nomes, primeiroNome);
System.out.println(nomes);
// =&gt; [Crispin Glover, Diane Ladd, Harry Stanton, Johnny
// Cage, Laura Dern, Metal Cage, Nicolas Cage, William
// Morgan]

Outra maneira de ordenar é usando TreeSet. Isso faz a ordenação de maneira automática, sem precisar chamar a função sort. Para funcionar os objetos da TreeSet devem ser “Comparable” ou você deve definir uma classe Comparator.

SortedSet&lt;NomeCompleto&gt; nomes = new TreeSet&lt;NomeCompleto&gt;(new PrimeiroNomePrimeiro());
nomes.add(new NomeCompleto("Laura", "Dern"));
nomes.add(new NomeCompleto("Harry", "Stanton"));
nomes.add(new NomeCompleto("Diane", "Ladd"));
System.out.println(nomes);
// =&gt; [Diane Ladd, Harry Stanton, Laura Dern]



Aprofundando

Procurar e estudar na documentação do java as classes apresentadas nesta aula: (veja java.util.*) em http://java.sun.com/j2se/1.5.0/docs/api