Nas suas aplicações Java, você geralmente 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 Vectores para armazenar e gerenciar um grupo de objetos. Mas eles tinham suas próprias desvantagens.
O Framework de Coleções Java 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 Framework de Coleções Java. Os Generics permitem que você garanta a segurança de tipos para objetos armazenados em uma coleção, o que aumenta a robustez de suas aplicações. Você pode ler mais sobre Generics em Java aqui.
Neste artigo, vou orientá-lo sobre como usar o Framework de Coleções Java. Vamos discutir os diferentes tipos de coleções, como Listas, Conjuntos, Filas e Mapas. Também fornecerei uma breve explicação de suas principais características, tais como:
-
Mecanismos internos
-
Tratamento de duplicatas
-
Suporte para valores nulos
-
Ordenação
-
Sincronização
-
Desempenho
-
Métodos-chave
-
Implementações comuns
Também vamos percorrer alguns exemplos de código para uma melhor compreensão, e vou abordar a classe de utilitário Collections e seu uso.
Sumário:
Compreendendo o Framework de Coleções 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 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, buscar e ordenar objetos de forma eficaz usando o Framework de Coleções 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 implementadora deve fornecer implementações concretas para todos os métodos declarados na interface.
No Framework de Coleções Java, várias interfaces de coleção como Set
, List
e Queue
estendem a interface Collection
e devem aderir ao contrato definido pela interface Collection
.
Decodificando a Hierarquia do Framework de Coleções Java
Confira este diagrama interessante deste artigo que ilustra a Hierarquia de Coleções Java:
Vamos começar de cima para baixo para que você possa entender o que este diagrama está mostrando:
-
No topo do Framework de Coleções Java está a interface
Iterable
, que permite iterar sobre os elementos de uma coleção. -
A interface
Collection
estende a interfaceIterable
. Isso significa que ela herda as propriedades e comportamentos da interfaceIterable
e adiciona seus próprios comportamentos 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 implementando seus métodos. Por exemplo,ArrayList
é uma implementação popular da interfaceList
,HashSet
implementa a interfaceSet
, e assim por diante. -
A interface
Map
faz 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 Framework de Coleções do Java gira em torno da diferença entre Collection
e Collections
. Collection
é uma interface no framework, enquanto Collections
é uma classe de utilitário. A classe Collections
fornece métodos estáticos que realizam operações nos elementos de uma coleção.
Interfaces de Coleção do Java
Até agora, 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 dos elementos durante a inserção e seu suporte para 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 é seu desempenho durante operações comuns como inserção, exclusão e recuperação. O desempenho é geralmente expresso usando notação Big-O. Você pode saber mais sobre isso aqui.
Listas
Uma Lista
é uma coleção ordenada ou sequencial de elementos. Ela 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
Lista
é suportada internamente 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. UmaLista
redimensiona-se dinamicamente com 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 múltiplos 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
Lista
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-chave: Aqui estão alguns métodos-chave de uma interface
List
:add(E elemento)
,get(int índice)
,set(int índice, E elemento)
,remove(int índice)
etamanho()
. Vamos ver como usar 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 List<String> lista = novo ArrayList<>(); // add(E elemento) lista.add("Maçã"); lista.add("Banana"); lista.add("Cereja"); // get(int índice) String segundoElemento = lista.get(1); // "Banana" // set(int índice, E elemento) lista.set(1, "Mirtilo"); // remove(int índice) lista.remover(0); // Remove "Maçã" // tamanho() int tamanho = lista.tamanho(); // 2 // Imprimir a lista Sistema.fora.println(lista); // Saída: [Mirtilo, Cereja] // Imprimir o tamanho da lista Sistema.fora.println(tamanho); // Saída: 2 } }
-
Implementações comuns:
ArrayList
,LinkedList
,Vector
,Stack
-
Desempenho: Normalmente, 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ápido no final – O(1) amortizado, lento no início ou meio – O(n) | Rápido no início ou meio – O(1), lento no final – O(n) |
Exclusão | Rápido no final – O(1) amortizado, lento no início ou meio – O(n) | Rápido – O(1) se a posição for conhecida |
Recuperação | Rápido – O(1) para acesso aleatório | Lento – 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
é internamente suportado 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. -
Duplicatas: 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 doTreeSet
, onde valores nulos não são permitidos. -
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 na 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 principais: Aqui estão alguns métodos principais de uma interface
Set
:add(E element)
,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("Maçã"); set.add("Banana"); set.add("Cereja"); // Remover um elemento do conjunto set.remove("Banana"); // Verificar se o conjunto contém um elemento boolean containsApple = set.contains("Maçã"); System.out.println("Contém Maçã: " + 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 desempenho rápido 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ápida – O(1) | Rápida – O(1) | Mais lenta – O(log n) |
Exclusão | Rápida – O(1) | Rápida – O(1) | Mais lenta – O(log n) |
Recuperação | Rápida – O(1) | Rápida – O(1) | Mais lenta – O(log n) |
Filas
Uma Fila
é uma coleção linear de elementos usada para armazenar vários 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 na outra, então o primeiro elemento adicionado à fila é o primeiro a ser removido.
-
Mecanismo interno: O funcionamento interno de uma
Fila
pode variar com base em sua implementação específica.-
LinkedList
– usa uma lista duplamente encadeada para armazenar elementos, o que significa que é possível percorrer tanto para frente quanto para trás, permitindo operações flexíveis. -
PriorityQueue
– é internamente suportada por um heap binário, que é muito eficiente para operações de recuperação. -
ArrayDeque
– é implementada usando um array que se expande ou encolhe conforme os 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 que múltiplas instâncias do mesmo valor sejam inseridas -
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 em 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 thread. -
Métodos-chave: Aqui estão alguns métodos-chave de uma interface
Fila
:add(E elemento)
,offer(E elemento)
,poll()
epeek()
. Vamos ver como usar esses métodos com um programa de exemplo.import java.util.LinkedList; import java.util.Queue; public class ExemploFila { public static void main(String[] args) { // Criar uma fila usando LinkedList Queue<String> fila = new LinkedList<>(); // Usar o método add para inserir elementos, lança exceção se a inserção falhar fila.add("Elemento1"); fila.add("Elemento2"); fila.add("Elemento3"); // Usar o método offer para inserir elementos, retorna falso se a inserção falhar fila.offer("Elemento4"); // Exibir fila System.out.println("Fila: " + fila); // Espiar o primeiro elemento (não o remove) String primeiroElemento = fila.peek(); System.out.println("Espiar: " + primeiroElemento); // saída "Elemento1" // Retirar o primeiro elemento (recupera e remove) String elementoRetirado = fila.poll(); System.out.println("Retirar: " + elementoRetirado); // saída "Elemento1" // Exibir fila após retirar System.out.println("Fila após retirar: " + fila); } }
-
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ápido no início ou meio – O(1), lento no final – O(n) | Mais lento – O(log n) | Rápido – O(1), Lento – O(n), se envolver redimensionamento do array interno |
Exclusão | Rápido – O(1) se a posição for conhecida | Mais lento – O(log n) | Rápido – O(1), Lento – O(n), se envolver redimensionamento do array interno |
Recuperação | Lento – O(n) para acesso aleatório, pois envolve percorrer | Rápido – O(1) | Rápido – O(1) |
Mapas
Um Map
representa uma coleção de pares chave-valor, com cada chave mapeando para um único valor. Embora Map
faça parte do framework de Coleções Java, ele não estende a interface java.util.Collection
.
-
Mecanismo interno: Um
Map
funciona internamente usando umaHashTable
com base no conceito de hashing. Escrevi um artigo detalhado sobre este tópico, então leia para uma compreensão mais profunda. -
Duplicatas: Um
Map
armazena dados como pares chave-valor. Aqui, cada chave é única, portanto, chaves duplicadas não são permitidas. Mas valores duplicados são permitidos. -
Nulo: Como chaves duplicadas não são permitidas, um
Map
pode ter apenas uma chave nula. Como valores duplicados são permitidos, ele pode ter múltiplos valores nulos. Na implementação doTreeMap
, chaves não podem ser nulas porque ele ordena 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
– Os elementos são inseridos com base na ordem natural deles. 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 garantir a segurança em threads. -
Métodos chave: Aqui estão alguns métodos chave de uma interface
Map
:put(K key, V value)
,get(Object key)
,remove(Object key)
,containsKey(Object key)
ekeySet()
. Vamos ver como usar esses métodos com um programa de exemplo.import java.util.HashMap; import java.util.Map; import java.util.Set; public class ExemploMapMethods { public static void main(String[] args) { // Criar um novo HashMap Map<String, Integer> map = new HashMap<>(); // put(K key, V value) - Insere pares chave-valor no mapa map.put("Maçã", 1); map.put("Banana", 2); map.put("Laranja", 3); // get(Object key) - Retorna o valor associado à chave Integer valor = map.get("Banana"); System.out.println("Valor para 'Banana': " + valor); // remove(Object key) - Remove o par chave-valor para a chave especificada map.remove("Laranja"); // containsKey(Object key) - Verifica se o mapa contém a chave especificada boolean temMaca = map.containsKey("Maçã"); System.out.println("Contém 'Maçã': " + temMaca); // keySet() - Retorna uma visualização de conjunto das chaves contidas no mapa Set<String> chaves = map.keySet(); System.out.println("Chaves no mapa: " + chaves); } }
-
Implementações comuns:
HashMap
,LinkedHashMap
,TreeMap
,Hashtable
,ConcurrentHashMap
-
Desempenho: A implementação
HashMap
é amplamente utilizada principalmente devido às suas características de desempenho eficientes mostradas na tabela abaixo.
Operação | HashMap | LinkedHashMap | TreeMap |
Inserção | Rápida – O(1) | Rápida – O(1) | Mais lenta – O(log n) |
Exclusão | Rápida – O(1) | Rápida – O(1) | Mais lenta – O(log n) |
Recuperação | Rápida – O(1) | Rápida – O(1) | Mais lenta – O(log n) |
Classe de Utilitário de Coleções
Conforme destacado no início deste artigo, a classe de utilitário 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 algumas características e métodos principais, juntamente com o que fazem, listados brevemente:
-
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>, key)
– este método é usado para buscar um elemento específico em uma lista ordenada e retornar seu índice. -
Ordem reversa:
Collections.reverse(List<T>)
– este método é usado para inverter a ordem dos elementos em uma lista. -
Operações Min/Max:
Collections.min(Collection<T>)
eCollections.max(Collection<T>)
– esses métodos são usados para encontrar, respectivamente, os elementos mínimo e máximo em uma coleção. -
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, evitando modificações.
Aqui está um exemplo de programa Java 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);
// Busca
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 de Mínimo/Máximo
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, busca, reversão, encontrar valores mínimos e máximos, sincronização e criação de uma lista imutável usando a classe utilitária Collections
.
Conclusão
Neste 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, obtendo insights valiosos para escolher as estruturas de dados certas para suas necessidades.
Ao entender esses conceitos, você pode utilizar plenamente o Java Collections Framework, permitindo que 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 se conectar comigo no LinkedIn.
Source:
https://www.freecodecamp.org/news/java-collections-framework-reference-guide/