A API de collections do Java é uma das partes mais utilizadas da plataforma Java, porque na maioria dos sistemas é necessário manipular conjuntos de dados, mas muitos programadores conhecem apenas as classes básicas dessa API, como a classe ArrayList, mas ela proporciona muito mais opções para se trabalhar com dados, como as classes HashSet e HashMap, que em determinadas situações podem ser opções melhores que as listas.

O objetivo desse artigo é apresentar as principais classes da API de collections do Java, entre elas, as listas, os conjuntos e os mapas, serão apresentados alguns exemplos práticos de como utilizar essas classes, e serão feitas algumas comparações de quando usar cada classe.

A API Collections

A API collections disponibiliza diversas funcionalidades para a manipulação de conjuntos de dados. Os principais componentes dessa API são:

  • Interfaces: Representam os principais tipos de coleções que a API java disponibiliza como Listas e Conjuntos.
  • Implementação: Classes concretas que implementam uma ou mais interfaces, que implementam algum tipo de coleção, como o ArrayList ou o HashSet.
  • Algoritmos: Implementação de alguns algoritmos disponíveis, como algoritmos para ordenação e busca.

Listas

É o tipo de coleção mais utilizado porque é mais fácil e simples que os outros. Todas as classes desse tipo de coleção implementam a interface java.util.List, e as duas implementações são as classes java.util.ArrayList e java.util.LinkedList. Nesse tipo de coleção os dados são alocados na ordem que eles foram adicionados.

Os métodos mais importantes dessa interface são:

  • Add: adiciona um objeto na lista;
  • Get: recupera um objeto da lista, passando como parâmetro o índice do objeto;
  • Remove: remove o objeto da lista.

A Listagem 1 mostra um exemplo de utilização do ArrayList.


package com.devmedia.lista;
 
import java.util.ArrayList;
import java.util.List;
 
public class MainArrayList {
       
       public static void main(String [] args) {
             List<String> nomes = new ArrayList<String>();
             nomes.add("Eduardo");
             nomes.add("Luiz");
             nomes.add("Bruna");
             nomes.add("Sonia");
             nomes.add("Brianda");
             nomes.add("Julia");
             nomes.add("Carlos");
 
 
             
             for (String nome : nomes) {
                    System.out.println(nome);
             }
       }
 
}
Listagem 1. Exemplo de utilização de um ArrayList

A diferença entre as classes ArrayList e LinkedList está na performance, pois o ArrayList é mais rápido na recuperação de dados, enquanto o LinkedList tem melhor performance na adição e exclusão de dados. A Listagem 2 mostra um exemplo de utilização da classe LinkedList.


package com.devmedia.lista;
 
import java.util.LinkedList;
import java.util.List;
 
public class MainLinkedList {
       
       public static void main(String [] args) {
             List<String> nomes = new LinkedList<String>();
             nomes.add("Eduardo");
             nomes.add("Luiz");
             nomes.add("Bruna");
             nomes.add("Sonia");
             nomes.add("Brianda");
             nomes.add("Julia");
             nomes.add("Carlos");
 
             
             for (String nome : nomes) {
                    System.out.println(nome);
             }
       }
 
}
Listagem 2. Exemplo de utilização de um LinkedList

A Figura 1 mostra a saída no console da execução dos códigos das Listagens 1 e 2 e, como esperado, o resultado é o mesmo para as duas execuções, já que as duas classes funcionam da mesma forma.

Execução dos exemplos com ArrayList e LinkedList
Figura 1. Execução dos exemplos com ArrayList e LinkedList

Conjuntos

Os conjuntos são coleções onde não existem dados repetidos. Caso dois objetos sejam iguais, considerando o método equals, apenas um será incluído no conjunto. Todas as classes desse tipo de coleção implementam a interface java.util.Set. Existem três implementações de conjunto: classes java.util.HashSet, java.util.LinkedHashSet e java.util.TreeSet.

A Listagem 3 mostra a utilização da classe HashSet, onde os dados do conjunto serão armazenados em uma tabela Hash. Nesse tipo de coleção, a ordem em que os dados serão retornados não é garantida.


package com.devmedia.conjuntos;
 
import java.util.HashSet;
import java.util.Set;
 
public class MainHashSet {
       
       public static void main(String [] args) {
             Set<String> nomes = new HashSet<String>();
             nomes.add("Eduardo");
             nomes.add("Luiz");
             nomes.add("Bruna");
             nomes.add("Sonia");
             nomes.add("Brianda");
             nomes.add("Julia");
             nomes.add("Carlos");
 
             
             for (String nome : nomes) {
                    System.out.println(nome);
             }
       }
 
}
Listagem 3. Exemplo de utilização de um HashSet

A Listagem 4 mostra a utilização da classe LinkedHashSet que é uma extensão da classe HashSet. A diferença é que nessa classe os dados são recuperados na ordem em que foram inseridos.


package com.devmedia.conjuntos;
 
import java.util.LinkedHashSet;
import java.util.Set;
 
public class MainLinkedHashSet {
       
       public static void main(String [] args) {
             Set<String> nomes = new LinkedHashSet<String>();
             nomes.add("Eduardo");
             nomes.add("Luiz");
             nomes.add("Bruna");
             nomes.add("Sonia");
             nomes.add("Brianda");
             nomes.add("Julia");
             nomes.add("Carlos");
 
             
             for (String nome : nomes) {
                    System.out.println(nome);
             }
       }
 
}
Listagem 4. Exemplo de utilização de um LinkedHashSet

A Listagem 5 mostra a utilização da classe TreeSet, onde os dados são armazenados em uma árvore são recuperados ordenados. Essa é a grande vantagem dessa classe.


package com.devmedia.conjuntos;
 
import java.util.Set;
import java.util.TreeSet;
 
public class MainTreeSet {
       
       public static void main(String [] args) {
             Set<String> nomes = new TreeSet<String>();
             nomes.add("Eduardo");
             nomes.add("Luiz");
             nomes.add("Bruna");
             nomes.add("Sonia");
             nomes.add("Brianda");
             nomes.add("Julia");
             nomes.add("Carlos");
 
             
             for (String nome : nomes) {
                    System.out.println(nome);
             }
       }
 
}
Listagem 5. Exemplo de utilização de um TreeSet

A Figura 2 mostra a execução do código das Listagens 3, 4 e 5 e, como podemos verificar, apesar dos dados terem sidos inseridos na mesma ordem, a saída é diferente nos três casos:

  • Com o HashSet, a ordem da saída é aleatória;
  • Com o LinkedHashSet a saída é na ordem da inserção dos dados, e
  • Com o TreeSet a saída é com a ordenação dos dados.
Execução com HashSet, LinkedHashSet e TreeSet
Figura 2. Execução com HashSet, LinkedHashSet e TreeSet

Mapas

Os mapas são coleções relacionadas as chaves com valores, com isso, quando for adicionar um novo objeto na coleção, é preciso dizer qual a chave para a busca desse objeto. Todas as classes desse tipo de coleção implementam a interface java.util.Map.

Existem três classes principais de mapas: o java.util.HashMap, o java.util.TreeMap e o java.util.LinkedHashMap.

A Listagem 6 mostra um exemplo da utilização de um HashMap, onde, para adicionar valores em um Map, é utilizado o método put, passados como parâmetro uma chave e um valor. O HashMap não tem uma ordenação específica e permite valores nulos tanto para a chave quanto para os valores armazenados.


package com.devmedia.mapas;
 
import java.util.HashMap;
import java.util.Map;
 
public class MainHashMap {
       
       public static void main(String [] args) {
             Map<Integer,String> nomes = new HashMap<Integer,String>();
             nomes.put(5, "Eduardo");
             nomes.put(3, "Luiz");
             nomes.put(2, "Bruna");
             nomes.put(4, "Sonia");
             nomes.put(1, "Brianda");
             nomes.put(7, "Julia");
             nomes.put(6, "Carlos");
 
             
             for (String nome : nomes.values()) {
                    System.out.println(nome);
             }
       }
 
}
Listagem 6. Exemplo de utilização do HashMap

A Listagem 7 apresenta um exemplo da utilização do TreeMap, onde a adição e a recuperação dos dados é igual à do HashMap. A diferença é que os dados no TreeMap são ordenados pela chave e apenas os valores armazenados podem ser nulos, mas a chave não.


package com.devmedia.mapas;
 
import java.util.Map;
import java.util.TreeMap;
 
public class MainTreeMap {
       
       public static void main(String [] args) {
             Map<Integer,String> nomes = new TreeMap<Integer,String>();
             nomes.put(5, "Eduardo");
             nomes.put(3, "Luiz");
             nomes.put(2, "Bruna");
             nomes.put(4, "Sonia");
             nomes.put(1, "Brianda");
             nomes.put(7, "Julia");
             nomes.put(6, "Carlos");
 
             
             for (String nome : nomes.values()) {
                    System.out.println(nome);
             }
       }
 
}
Listagem 7. Exemplo de utilização do TreeMap

A Listagem 8 apresenta um exemplo da utilização do LinkedHashMap, onde a adição e a recuperação dos dados é igual à do HashMap e do TreeMap, mas a diferença é que os dados no TreeMap são ordenados pela ordem de adição dos valores no mapa e, assim como o TreeMap, apenas os valores armazenados podem ser nulos, mas a chave não.


package com.devmedia.mapas;
 
import java.util.LinkedHashMap;
import java.util.Map;
 
public class MainLinkedHashMap {
       
       public static void main(String [] args) {
             Map<Integer,String> nomes = new LinkedHashMap<Integer,String>();
             nomes.put(5, "Eduardo");
             nomes.put(3, "Luiz");
             nomes.put(2, "Bruna");
             nomes.put(4, "Sonia");
             nomes.put(1, "Brianda");
             nomes.put(7, "Julia");
             nomes.put(6, "Carlos");
 
             
             for (String nome : nomes.values()) {
                    System.out.println(nome);
             }
       }
 
}
Listagem 8. Exemplo de utilização do LinkedHashMap

A Figura 3 mostra a execução do código das Listagens 6, 7 e 8 e, como podemos verificar, apesar dos dados terem sidos inseridos na mesma ordem, a saída é diferente nos três casos:

  • Com o HashMap, a ordem da saída é aleatória;
  • Com o LinkedHashMap, a saída é na ordem da inserção dos dados;
  • Com o TreeMap, a saída é com a ordenação dos dados.
Execução com HashMap, LinkedHashMap e TreeMap
Figura 3. Execução com HashMap, LinkedHashMap e TreeMap

Algoritmos

A API collections disponibiliza uma série de algoritmos pré implementados para serem utilizados sobre as coleções. Alguns algoritmos são bastante utilizados, como a ordenação dos dados, e outros são pouco conhecidos e utilizados, como o algoritmo de busca binaria. A Tabela 1 apresenta alguns dos algoritmos que são disponibilizados.

O primeiro algoritmo apresentado é o para a ordenação dos dados. Para isso, é utilizado o método sort da classe Collections e como parâmetro são passados a lista a ser ordenada e um objeto do tipo Comparator, que indica como deve ser feita a ordenação.

Para objetos Integer e String, já existe um Comparator padrão, mas para classes definidas pelo programador, também deve ser implementado um Comparator. A Listagem 9 mostra a implementação da ordenação dos dados.


package com.devmedia.algortimos;
 
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
 
public class MainOrdenacao {
       public static void main(String args[]) {
 
             LinkedList<Integer> lista = new LinkedList<Integer>();
             lista.add(new Integer(10));
             lista.add(new Integer(20));
             lista.add(new Integer(5));
             lista.add(new Integer(3));
             lista.add(new Integer(10));
             lista.add(new Integer(-5));
             lista.add(new Integer(6));              
lista.add(new Integer(8));
             lista.add(new Integer(-2));
             lista.add(new Integer(4));
 
             
             System.out.println("Lista na ordem crescente:");
             Collections.sort(lista);
             for (Integer i : lista) {
                    System.out.println(i);
             }
             
             System.out.println("Lista na ordem crescente:");
             Comparator<Integer> r = Collections.reverseOrder();
             Collections.sort(lista, r);
             for (Integer i : lista) {
                    System.out.println(i);
             }
       
       }
}
Listagem 9. Ordenação dos dados

Outra funcionalidade bastante útil é descobrir o valor mínimo e o valor máximo de uma coleção: para isso, podem ser utilizados os métodos min e max da classe Collections. O único parâmetro é a lista que se quer analisar. A Listagem 10 exibe um exemplo de código onde são escritos o maior e menor valor da lista.


package com.devmedia.algortimos;
 
import java.util.Collections;
import java.util.LinkedList;
 
public class MainMinMax {
 
       public static void main(String[] args) {
 
             LinkedList<Integer> lista = new LinkedList<Integer>();
             lista.add(new Integer(10));
             lista.add(new Integer(20));
             lista.add(new Integer(5));
             lista.add(new Integer(3));
             lista.add(new Integer(10));
             lista.add(new Integer(-5));
             lista.add(new Integer(6));              
lista.add(new Integer(8));
             lista.add(new Integer(-2));
             lista.add(new Integer(4)); 
             lista.add(new Integer(12));
 
 
             System.out.println("O valor minimo é: " + Collections.min(lista));
             System.out.println("O valor maximo é: " + Collections.max(lista));
       }
 
}
Listagem 10. Busca do elemento mínimo e máximo de uma lista

A API Collections disponibiliza uma implementação de busca binária, para isso, é necessário utilizar o método binarySearch da classe Collections. Os parâmetros devem ser passados a lista onde se vai fazer a busca e qual o elemento buscado. A lista passada como parâmetro deve estar ordenada para que o algoritmo funcione. A Listagem 11 mostra o código para utilizar a busca binária.


package com.devmedia.algortimos;
 
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
 
import javax.swing.JOptionPane;
 
public class MainBinarySearch {
 
       public static void main(String args[]) {
             LinkedList<Integer> lista = new LinkedList<Integer>();
             lista.add(new Integer(10));
             lista.add(new Integer(20));
             lista.add(new Integer(5));
             lista.add(new Integer(3));
             lista.add(new Integer(10));
             lista.add(new Integer(-5));
             lista.add(new Integer(6));              
             lista.add(new Integer(8));
             lista.add(new Integer(-2));
             lista.add(new Integer(4)); 
             lista.add(new Integer(12));
 
             
             System.out.println("Lista na ordem crescente:");
             Collections.sort(lista);
             
             int valor = Integer.parseInt(JOptionPane
             .showInputDialog("Digite o valor procurado:"));
             
             int indice = Collections.binarySearch(lista, valor);
             System.out.println("O item buscado esta na posicao: " + indice);
       
       }
}
Listagem 11. Busca binária em uma lista

O método swap da classe Collections troca a posição de elementos em uma lista. Esse método pode ser útil quando se quer trocar a posição de elementos da lista, sem ter que excluir e incluir novamente os elementos. A Listagem 12 mostra o código da utilização do método swap.


package com.devmedia.algortimos;
 
import java.util.Collections;
import java.util.LinkedList;
 
import javax.swing.JOptionPane;
 
public class MainOrdenacao {
       
       public static void main(String args[]) {
             LinkedList<Integer> lista = 
             new LinkedList<Integer>();
             lista.add(new Integer(10));
             lista.add(new Integer(20));
             lista.add(new Integer(5));
             lista.add(new Integer(3));
             lista.add(new Integer(10));
             lista.add(new Integer(-5));
             lista.add(new Integer(6));              
             lista.add(new Integer(8));
             lista.add(new Integer(-2));
             lista.add(new Integer(4));
             lista.add(new Integer(12));
 
 
             
             int pos1 = Integer.parseInt(JOptionPane
             .showInputDialog("Digite a primeira posição:"));
             int pos2 = Integer.parseInt(JOptionPane
             .showInputDialog("Digite a segunda posição:"));
             
             Collections.swap(lista, pos1, pos2);
             
             for (Integer i : lista) {
                    System.out.println(i);
             }
       
       }
}
Listagem 12. Utilização do método swap

O método Rotate da classe Collections desloca todos os elementos da lista uma determinada quantidade de posições. Os parâmetros desse método são a lista que se quer rotacionar e um inteiro com a quantidade de vezes que os elementos serão deslocados. Com esse método a lista funciona como uma lista circular, com isso, os últimos elementos da lista vão para as primeiras posições. A Listagem 13 apresenta o código para a utilização do método rotate.


package com.devmedia.algortimos;
 
import java.util.Collections;
import java.util.LinkedList;
 
import javax.swing.JOptionPane;
 
public class MainOrdenacao {
       
   public static void main(String args[]) {
     LinkedList<Integer> lista = new LinkedList<Integer>();
     lista.add(new Integer(10));
     lista.add(new Integer(20));
     lista.add(new Integer(5));
     lista.add(new Integer(3));
     lista.add(new Integer(10));
     lista.add(new Integer(-5));
     lista.add(new Integer(6));              
     lista.add(new Integer(8));
     lista.add(new Integer(-2));
     lista.add(new Integer(4)); 
     lista.add(new Integer(12));


     
     int valor = Integer.parseInt(JOptionPane
     .showInputDialog("Digite o número de descolamento da lista:"));
     Collections.rotate(lista, valor);
     for (Integer i : lista) {
            System.out.println(i);
     }
   
   }
}
Listagem 13. Utilização do método rotate

O método shuffle da classe collections embaralha randomicamente os dados de uma lista e o único parâmetro desse método é a própria lista que será embaralhada. A Listagem 14 exibe o código da utilização desse método.


package com.devmedia.algortimos;
 
import java.util.Collections;
import java.util.LinkedList;
 
import javax.swing.JOptionPane;
 
public class MainShuffle {
 
   public static void main(String args[]) {
     LinkedList<Integer> lista = new LinkedList<Integer>();
     lista.add(new Integer(10));
     lista.add(new Integer(20));
     lista.add(new Integer(5));
     lista.add(new Integer(3));
     lista.add(new Integer(10));
     lista.add(new Integer(-5));
     lista.add(new Integer(6));              
     lista.add(new Integer(8));
     lista.add(new Integer(-2));
     lista.add(new Integer(4)); 
     lista.add(new Integer(12));


     Collections.shuffle(lista);
     
     for (Integer i : lista) {
            System.out.println(i);
     }
   
   }
       
}
Listagem 14. Utilização do método shuffle

Este artigo apresentou as principais funcionalidades da API de coleções do Java, que é uma das principais funcionalidades da plataforma. Os principais tipos de coleções são as listas, os conjuntos e os mapas. Apesar de parecidas, cada uma tem alguma particularidade que as torna melhores que as outras em determinadas situações.

Foram apresentados também diversos algoritmos que estão disponíveis na classe java.util.Collections, como a busca binária, a ordenação e a busca por elementos mínimos e máximos.