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);
}
}
}
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);
}
}
}
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.
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);
}
}
}
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);
}
}
}
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);
}
}
}
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.
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);
}
}
}
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);
}
}
}
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);
}
}
}
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.
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);
}
}
}
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));
}
}
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);
}
}
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);
}
}
}
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);
}
}
}
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);
}
}
}
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.