Nas suas aplicações Java, normalmente você trabalhará com vários tipos de objetos. E você pode querer realizar operações como ordenação, busca e iteração nesses objetos.
Antes da introdução do framework Collections no JDK 1.2, você teria usado Arrays e Vectors para armazenar e gerenciar um grupo de objetos. Mas eles tinham suas próprias desvantagens.
O Java Collections Framework tem como objetivo superar esses problemas, fornecendo implementações de alto desempenho de estruturas de dados comuns. Isso permite que você se concentre em escrever a lógica da aplicação em vez de se concentrar em operações de baixo nível.
Em seguida, a introdução de Generics no JDK 1.5 melhorou significativamente o Java Collections Framework. Os Generics permitem que você aplique segurança de tipo aos objetos armazenados em uma coleção, o que melhora a robustez das suas aplicações. Você pode ler mais sobre Generics em Java aqui.
Neste artigo, vou orientá-lo sobre como usar o Java Collections Framework. Vamos discutir os diferentes tipos de coleções, como Listas, Conjuntos, Filas e Mapas. Também vou fornecer uma breve explicação de suas principais características, como:
-
Mecanismos internos
-
Tratamento de duplicados
-
Suporte para valores nulos
-
Ordenação
-
Sincronização
-
Desempenho
-
Métodos chave
-
Implementações comuns
Também iremos percorrer alguns exemplos de código para uma melhor compreensão, e vou abordar a classe utilitária Collections e seu uso.
Sumário:
Compreendendo o Framework de Coleções do Java
De acordo com a documentação do Java, “Uma coleção é um objeto que representa um grupo de objetos. Um framework de coleções é uma arquitetura unificada para representar e manipular coleções.”
Em termos simples, o Framework de Coleções do Java ajuda a gerenciar um grupo de objetos e realizar operações sobre eles de forma eficiente e organizada. Ele facilita o desenvolvimento de aplicações oferecendo vários métodos para lidar com grupos de objetos. Você pode adicionar, remover, pesquisar e ordenar objetos de forma eficaz usando o Framework de Coleções do Java.
Interfaces de Coleção
Em Java, uma interface especifica um contrato que deve ser cumprido por qualquer classe que a implemente. Isso significa que a classe que implementa deve fornecer implementações concretas para todos os métodos declarados na interface.
No Framework de Coleções do Java, várias interfaces de coleção como Set
, List
e Queue
estendem a interface Collection
, e elas devem aderir ao contrato definido pela interface Collection
.
Descodificando a Hierarquia do Framework de Coleções do Java
Confira este diagrama interessante deste artigo que ilustra a Hierarquia de Coleções do Java:
Vamos começar do topo e trabalhar para baixo para que você possa entender o que este diagrama está mostrando:
-
No topo do Framework de Coleções do Java está a interface
Iterable
, que permite iterar sobre os elementos de uma coleção. -
A interface
Collection
estende a interfaceIterable
. Isso significa que herda as propriedades e comportamentos da interfaceIterable
e adiciona seu próprio comportamento para adicionar, remover e recuperar elementos. -
Interfaces específicas como
List
,Set
eQueue
estendem ainda mais a interfaceCollection
. Cada uma dessas interfaces possui outras classes que implementam seus métodos. Por exemplo,ArrayList
é uma implementação popular da interfaceList
,HashSet
implementa a interfaceSet
, e assim por diante. -
A interface
Map
é parte do Framework de Coleções do Java, mas não estende a interfaceCollection
, ao contrário das outras mencionadas acima. -
Todas as interfaces e classes neste framework fazem parte do pacote
java.util
.
Nota: Uma fonte comum de confusão no Java Collections Framework gira em torno da diferença entre Collection
e Collections
. Collection
é uma interface no framework, enquanto Collections
é uma classe utilitária. A classe Collections
fornece métodos estáticos que realizam operações nos elementos de uma coleção.
Interfaces de Coleção Java
Neste ponto, você está familiarizado com os diferentes tipos de coleções que formam a base do framework de coleções. Agora vamos dar uma olhada mais de perto nas interfaces List
, Set
, Queue
e Map
.
Nesta seção, discutiremos cada uma dessas interfaces enquanto exploramos seus mecanismos internos. Vamos examinar como lidam com elementos duplicados e se suportam a inserção de valores nulos. Também vamos entender a ordenação de elementos durante a inserção e seu suporte à sincronização, que lida com o conceito de segurança de threads. Em seguida, vamos percorrer alguns métodos-chave dessas interfaces e concluir revisando implementações comuns e seu desempenho para várias operações.
Antes de começarmos, vamos falar brevemente sobre Sincronização e Desempenho.
-
A Sincronização controla o acesso a objetos compartilhados por várias threads, garantindo sua integridade e prevenindo conflitos. Isso é crucial para manter a segurança de threads.
-
Ao escolher um tipo de coleção, um fator importante é o desempenho durante operações comuns como inserção, exclusão e recuperação. O desempenho é geralmente expresso usando a notação Big-O. Você pode aprender mais sobre isso aqui.
Listas
Uma List
é uma coleção ordenada ou sequencial de elementos. Segue a indexação baseada em zero, permitindo que os elementos sejam inseridos, removidos ou acessados usando sua posição de índice.
-
Mecanismo interno: Uma
List
é internamente suportada por um array ou uma lista encadeada, dependendo do tipo de implementação. Por exemplo, umArrayList
usa um array, enquanto umLinkedList
usa uma lista encadeada internamente. Você pode ler mais sobreLinkedList
aqui. UmaList
redimensiona dinamicamente a si mesma após a adição ou remoção de elementos. A recuperação baseada em indexação a torna um tipo de coleção muito eficiente. -
Duplicatas: Elementos duplicados são permitidos em uma
List
, o que significa que pode haver mais de um elemento em umaList
com o mesmo valor. Qualquer valor pode ser recuperado com base no índice em que está armazenado. -
Nulo: Valores nulos também são permitidos em uma
List
. Como duplicatas são permitidas, você também pode ter vários elementos nulos. -
Ordenação: Uma
List
mantém a ordem de inserção, o que significa que os elementos são armazenados na mesma ordem em que são adicionados. Isso é útil quando você deseja recuperar elementos na ordem exata em que foram inseridos. -
Sincronização: Uma
List
não é sincronizada por padrão, o que significa que não possui uma maneira integrada de lidar com o acesso por várias threads ao mesmo tempo. -
Métodos principais: Aqui estão alguns métodos principais de uma interface
List
:add(E elemento)
,get(int índice)
,set(int índice, E elemento)
,remove(int índice)
etamanho()
. Vamos ver como utilizar esses métodos com um programa de exemplo.importar java.util.ArrayList; importar java.util.List; público classe ExemploLista { público estático vazio principal(String[] args) { // Criar uma lista Lista<String> lista = novo ArrayList<>(); // add(E elemento) lista.adicionar("Maçã"); lista.adicionar("Banana"); lista.adicionar("Cereja"); // get(int índice) String segundoElemento = lista.obter(1); // "Banana" // set(int índice, E elemento) lista.definir(1, "Mirtilo"); // remove(int índice) lista.remover(0); // Remove "Maçã" // tamanho() int tamanho = lista.tamanho(); // 2 // Imprimir a lista Sistema.paraFora.println(lista); // Saída: [Mirtilo, Cereja] // Imprimir o tamanho da lista Sistema.paraFora.println(tamanho); // Saída: 2 } }
-
Implementações comuns:
ArrayList
,LinkedList
,Vector
,Stack
-
Desempenho: Tipicamente, as operações de inserção e exclusão são rápidas tanto em
ArrayList
quanto emLinkedList
. Mas a recuperação de elementos pode ser lenta, pois é necessário percorrer os nós.
Operação | ArrayList | LinkedList |
Inserção | Rápida no final – O(1) amortizado, lenta no início ou meio – O(n) | Rápida no início ou meio – O(1), lenta no final – O(n) |
Exclusão | Rápida no final – O(1) amortizado, lenta no início ou meio – O(n) | Rápida – O(1) se a posição for conhecida |
Recuperação | Rápida – O(1) para acesso aleatório | Lenta – O(n) para acesso aleatório, pois envolve percorrer |
Conjuntos
Um Set
é um tipo de coleção que não permite elementos duplicados e representa o conceito de um conjunto matemático.
-
Mecanismo interno: Um
Set
é suportado internamente por umHashMap
. Dependendo do tipo de implementação, ele é suportado por umHashMap
,LinkedHashMap
ouTreeMap
. Eu escrevi um artigo detalhado sobre como oHashMap
funciona internamente aqui. Não deixe de conferir. -
Duplicados: Como um
Set
representa o conceito de um conjunto matemático, elementos duplicados não são permitidos. Isso garante que todos os elementos sejam únicos, mantendo a integridade da coleção. -
Nulo: É permitido no máximo um valor nulo em um
Set
porque duplicatas não são permitidas. Mas isso não se aplica à implementação deTreeSet
, onde valores nulos não são permitidos de forma alguma. -
Ordenação: A ordenação dos elementos em um
Set
depende do tipo de implementação.-
HashSet
: A ordem não é garantida, e os elementos podem ser colocados em qualquer posição. -
LinkedHashSet
: Esta implementação mantém a ordem de inserção, então você pode recuperar os elementos na mesma ordem em que foram inseridos. -
TreeSet
: Os elementos são inseridos com base em sua ordem natural. Alternativamente, você pode controlar a ordem de inserção especificando um comparador personalizado.
-
-
Sincronização: Um
Set
não é sincronizado, o que significa que você pode encontrar problemas de concorrência, como condições de corrida, que podem afetar a integridade dos dados se dois ou mais threads tentarem acessar um objetoSet
simultaneamente -
Métodos chave: Aqui estão alguns métodos chave de uma interface
Set
:add(E elemento)
,remove(Object o)
,contains(Object o)
esize()
. Vamos ver como usar esses métodos com um programa de exemplo.import java.util.HashSet; import java.util.Set; public class SetExample { public static void main(String[] args) { // Criar um conjunto Set<String> set = new HashSet<>(); // Adicionar elementos ao conjunto set.add("Apple"); set.add("Banana"); set.add("Cherry"); // Remover um elemento do conjunto set.remove("Banana"); // Verificar se o conjunto contém um elemento boolean containsApple = set.contains("Apple"); System.out.println("Contém Apple: " + containsApple); // Obter o tamanho do conjunto int size = set.size(); System.out.println("Tamanho do conjunto: " + size); } }
-
Implementações comuns:
HashSet
,LinkedHashSet
,TreeSet
-
Desempenho: As implementações de
Set
oferecem alto desempenho para operações básicas, exceto para umTreeSet
, onde o desempenho pode ser relativamente mais lento porque a estrutura de dados interna envolve a ordenação dos elementos durante essas operações.
Operação | HashSet | LinkedHashSet | TreeSet |
Inserção | Rápido – O(1) | Rápido – O(1) | Mais lento – O(log n) |
Deleção | Rápido – O(1) | Rápido – O(1) | Mais lento – O(log n) |
Recuperação | Rápido – O(1) | Rápido – O(1) | Mais lento – O(log n) |
Filas
Uma Queue
é uma coleção linear de elementos usada para armazenar múltiplos itens antes do processamento, geralmente seguindo a ordem FIFO (primeiro a entrar, primeiro a sair). Isso significa que os elementos são adicionados em uma extremidade e removidos da outra, então o primeiro elemento adicionado à fila é o primeiro a ser removido.
-
Mecanismo interno: O funcionamento interno de uma
Queue
pode diferir com base em sua implementação específica.-
LinkedList
– usa uma lista duplamente encadeada para armazenar elementos, o que significa que você pode percorrer tanto para frente quanto para trás, permitindo operações flexíveis. -
PriorityQueue
– é sustentada internamente por um heap binário, que é muito eficiente para operações de recuperação. -
ArrayDeque
– é implementada usando um array que expande ou diminui à medida que elementos são adicionados ou removidos. Aqui, os elementos podem ser adicionados ou removidos de ambas as extremidades da fila.
-
-
Duplicatas: Em uma
Fila
, elementos duplicados são permitidos, permitindo a inserção de várias instâncias do mesmo valor -
Nulo: Você não pode inserir um valor nulo em uma
Fila
porque, por design, alguns métodos de umaFila
retornam nulo para indicar que está vazia. Para evitar confusão, valores nulos não são permitidos. -
Ordenação: Os elementos são inseridos com base na sua ordem natural. Alternativamente, você pode controlar a ordem de inserção especificando um comparador personalizado.
-
Sincronização: Uma
Fila
não é sincronizada por padrão. No entanto, você pode usar uma implementação deConcurrentLinkedQueue
ouBlockingQueue
para garantir a segurança de threads. -
Métodos principais: Aqui estão alguns métodos principais de uma interface
Queue
:add(E elemento)
,offer(E elemento)
,poll()
epeek()
. Vamos ver como usar esses métodos com um exemplo de programa.import java.util.LinkedList; import java.util.Queue; public class QueueExample { public static void main(String[] args) { // Cria uma fila usando LinkedList Queue<String> queue = new LinkedList<>(); // Usa o método add para inserir elementos, lança exceção se a inserção falhar queue.add("Elemento1"); queue.add("Elemento2"); queue.add("Elemento3"); // Usa o método offer para inserir elementos, retorna falso se a inserção falhar queue.offer("Elemento4"); // Exibe a fila System.out.println("Fila: " + queue); // Olha para o primeiro elemento (não o remove) String firstElement = queue.peek(); System.out.println("Olhar: " + firstElement); // produz "Elemento1" // Remove o primeiro elemento (recupera e remove) String polledElement = queue.poll(); System.out.println("Remover: " + polledElement); // produz "Elemento1" // Exibe a fila após a remoção System.out.println("Fila após remoção: " + queue); } }
-
Implementações comuns:
LinkedList
,PriorityQueue
,ArrayDeque
-
Desempenho: Implementações como
LinkedList
eArrayDeque
geralmente são rápidas para adicionar e remover itens. APriorityQueue
é um pouco mais lenta porque insere itens com base na ordem de prioridade definida.
Operação | LinkedList | PriorityQueue | ArrayDeque |
Inserção | Rápida no início ou meio – O(1), lenta no final – O(n) | Mais lenta – O(log n) | Rápida – O(1), Lenta – O(n), se envolver redimensionamento do array interno |
Remoção | Rápida – O(1) se a posição for conhecida | Mais lenta – O(log n) | Rápida – O(1), Lenta – O(n), se envolver redimensionamento do array interno |
Recuperação | Lenta – O(n) para acesso aleatório, pois envolve percorrer | Rápida – O(1) | Rápida – O(1) |
Mapas
Um Map
representa uma coleção de pares de chave-valor, com cada chave mapeando para um único valor. Embora o Map
faça parte do framework de Coleções do Java, ele não estende a interface java.util.Collection
.
-
Mecanismo interno: Um
Mapa
funciona internamente usando umHashTable
com base no conceito de hash. Escrevi um artigo detalhado sobre esse tópico, então dê uma lida para uma compreensão mais profunda. -
Duplicatas: Um
Mapa
armazena dados como pares de chave-valor. Aqui, cada chave é única, então chaves duplicadas não são permitidas. Mas valores duplicados são permitidos. -
Nulo: Como chaves duplicadas não são permitidas, um
Mapa
pode ter apenas uma chave nula. Como valores duplicados são permitidos, ele pode ter vários valores nulos. Na implementação doTreeMap
, as chaves não podem ser nulas porque ele classifica os elementos com base nas chaves. No entanto, valores nulos são permitidos. -
Ordenação: A ordem de inserção de um
Map
varia de acordo com a implementação:-
HashMap
– a ordem de inserção não é garantida, pois ela é determinada com base no conceito de hash. -
LinkedHashMap
– a ordem de inserção é preservada e você pode recuperar os elementos na mesma ordem em que foram adicionados à coleção. -
TreeMap
– Elementos são inseridos com base na ordem natural. Alternativamente, você pode controlar a ordem de inserção especificando um comparador personalizado.
-
-
Sincronização: Um
Map
não é sincronizado por padrão. Mas você pode usarCollections.synchronizedMap()
ou implementações deConcurrentHashMap
para alcançar segurança de thread. -
Métodos principais: Aqui estão alguns métodos principais de uma
Map
interface:put(K key, V value)
,get(Object key)
,remove(Object key)
,containsKey(Object key)
, ekeySet()
. Vamos ver como usar esses métodos com um exemplo de programa.import java.util.HashMap; import java.util.Map; import java.util.Set; public class MapMethodsExample { public static void main(String[] args) { // Cria um novo HashMap Map<String, Integer> map = new HashMap<>(); // put(K key, V value) - Insere pares chave-valor no mapa map.put("Apple", 1); map.put("Banana", 2); map.put("Orange", 3); // get(Object key) - Retorna o valor associado à chave Integer value = map.get("Banana"); System.out.println("Valor de 'Banana': " + value); // remove(Object key) - Remove o par chave-valor para a chave especificada map.remove("Orange"); // containsKey(Object key) - Verifica se o mapa contém a chave especificada boolean hasApple = map.containsKey("Apple"); System.out.println("Contém 'Apple': " + hasApple); // keySet() - Retorna uma visão em conjunto das chaves contidas no mapa Set<String> keys = map.keySet(); System.out.println("Chaves no mapa: " + keys); } }
-
Implementações comuns:
HashMap
,LinkedHashMap
,TreeMap
,Hashtable
,ConcurrentHashMap
-
Desempenho: A implementação
HashMap
é amplamente utilizada principalmente devido às suas características de desempenho eficientes retratadas na tabela abaixo.
Operação | HashMap | LinkedHashMap | TreeMap |
Inserção | Rápido – O(1) | Rápido – O(1) | Mais lento – O(log n) |
Deleção | Rápido – O(1) | Rápido – O(1) | Mais lento – O(log n) |
Recuperação | Rápido – O(1) | Rápido – O(1) | Mais lento – O(log n) |
Classe Utilitária Collections
Como destacado no início deste artigo, a classe utilitária Collections
possui vários métodos estáticos úteis que permitem realizar operações comumente usadas nos elementos de uma coleção. Esses métodos ajudam a reduzir o código repetitivo em sua aplicação e permitem que você se concentre na lógica de negócios.
Aqui estão alguns recursos e métodos principais, juntamente com o que eles fazem, listados de forma breve:
-
Ordenação:
Collections.sort(List<T>)
– este método é usado para ordenar os elementos de uma lista em ordem crescente. -
Busca:
Collections.binarySearch(List<T>, chave)
– este método é usado para buscar um elemento específico em uma lista ordenada e retornar o seu índice. -
Ordem reversa:
Collections.reverse(List<T>)
– este método é usado para inverter a ordem dos elementos em uma lista. -
Operações de Mín/Máx:
Collections.min(Collection<T>)
eCollections.max(Collection<T>)
– esses métodos são usados para encontrar os elementos mínimo e máximo em uma coleção, respectivamente. -
Sincronização:
Collections.synchronizedList(List<T>)
– este método é usado para tornar uma lista segura para threads, sincronizando-a. -
Coleções Imutáveis:
Collections.unmodifiableList(List<T>)
– este método é usado para criar uma visão somente leitura de uma lista, impedindo modificações.
Aqui está um programa Java de exemplo que demonstra várias funcionalidades da classe utilitária Collections
:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(3);
numbers.add(8);
numbers.add(1);
// Ordenação
Collections.sort(numbers);
System.out.println("Sorted List: " + numbers);
// Pesquisa
int index = Collections.binarySearch(numbers, 3);
System.out.println("Index of 3: " + index);
// Ordem Reversa
Collections.reverse(numbers);
System.out.println("Reversed List: " + numbers);
// Operações Min/Max
int min = Collections.min(numbers);
int max = Collections.max(numbers);
System.out.println("Min: " + min + ", Max: " + max);
// Sincronização
List<Integer> synchronizedList = Collections.synchronizedList(numbers);
System.out.println("Synchronized List: " + synchronizedList);
// Coleções Imutáveis
List<Integer> unmodifiableList = Collections.unmodifiableList(numbers);
System.out.println("Unmodifiable List: " + unmodifiableList);
}
}
Este programa demonstra ordenação, pesquisa, reversão, busca de valores mínimos e máximos, sincronização e criação de uma lista imutável usando a classe utilitária Collections
.
Conclusão
No artigo, você aprendeu sobre o Framework de Coleções do Java e como ele ajuda a gerenciar grupos de objetos em aplicações Java. Exploramos vários tipos de coleções como Listas, Conjuntos, Filas e Mapas e obtivemos uma visão sobre algumas das características principais e como cada um desses tipos as suporta.
Você aprendeu sobre desempenho, sincronização e métodos chave, adquirindo insights valiosos para escolher as estruturas de dados certas para suas necessidades.
Ao entender esses conceitos, você pode aproveitar ao máximo o Java Collections Framework, permitindo que você escreva um código mais eficiente e construa aplicações robustas.
Se você achou este artigo interessante, sinta-se à vontade para conferir meus outros artigos no freeCodeCamp e conectar-se comigo no LinkedIn.
Source:
https://www.freecodecamp.org/news/java-collections-framework-reference-guide/