Conheça a API Java Collections

A API de coleções é um dos pontos fortes da plataforma Java, pois são disponibilizadas diversas maneiras de gerenciar dados, como listas, conjuntos e mapas com diversos métodos disponíveis que facilitam a manipulação desses dados.

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:

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:

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.

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:

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:

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.

Artigos relacionados