A public interface Collection<E> extends Iterable<E>
em Java, introduzida no JDK 1.2 (Java 2), é a raiz do que chamamos de hierarquia de coleções. Ela define o comportamento básico para a manipulação de coleções, ou seja, conjuntos de objetos conhecidos como elementos. Essa interface fornece métodos essenciais para operações como adicionar, remover e consultar elementos, criando uma base padronizada para lidar com grupos de dados.
Uma collection representa, essencialmente, um grupo de objetos, permitindo que o Java organize e manipule dados de forma eficiente. Embora a JDK não implemente diretamente a interface
Collection, ela define subinterfaces que representam diferentes tipos de coleções, como Set
, List
e Queue
. Essas subinterfaces oferecem comportamentos específicos para necessidades variadas, mantendo a consistência em toda a hierarquia de coleções.
Subinterfaces
Cada subinterface da interface principal Collection representa um conjunto de características próprias, que são implementadas em classes concretas, adaptadas a diferentes cenários de uso:
List
: Permite elementos duplicados e mantém a ordem de inserção. Exemplo de implementações incluem ArrayList
e LinkedList
. Esse conjunto de características é útil para listas onde a ordem dos elementos é importante e podem haver duplicatas.
Set
: Essa categoria de conjunto não permite elementos duplicados. Ideal para garantir que todos os elementos de uma coleção sejam únicos, como HashSet
e TreeSet
.
Queue
: Estrutura de dados baseada no princípio FIFO (First-In, First-Out), usada em tarefas que exigem processamento ordenado de elementos, como PriorityQueue
.
Multithreading
O comportamento das coleções durante a iteração pode ser classificado em duas categorias principais: fail-fast e fail-safe. Coleções fail-fast, como ArrayList
, HashSet
e HashMap
, são projetadas para lançar uma exceção, especificamente a ConcurrentModificationException
, quando a coleção é modificada enquanto está sendo iterada. Isso ocorre porque essas coleções monitoram alterações estruturais e, ao detectar modificações, interrompem a iteração para evitar resultados inconsistentes.
1 import java.util.ArrayList;
2 import java.util.Iterator;
3 import java.util.List;
4
5 public class FailFastExample {
6 public static void main(String[] args) {
7 List<String> lista = new ArrayList<>();
8 lista.add("A");
9 lista.add("B");
10 lista.add("C");
11
12 Iterator<String> it = lista.iterator();
13 while (it.hasNext()) {
14 String valor = it.next();
15 System.out.println(valor);
16
17 if ("B".equals(valor)) {
18 lista.add("D");
19 }
20 }
21 }
22 }
Repare que ao tentarmos executar esse código, conseguiremos exatamente a exceção que descrevemos acima.
Por outro lado, coleções fail-safe, como CopyOnWriteArrayList
e ConcurrentHashMap
, permitem que a coleção seja modificada enquanto é iterada sem lançar exceções, criando uma cópia interna da coleção na hora da iteração.
1 import java.util.Iterator;
2 import java.util.concurrent.CopyOnWriteArrayList;
3
4 public class FailSafeExample {
5 public static void main(String[] args) {
6 CopyOnWriteArrayList<String> lista = new CopyOnWriteArrayList<>();
7 lista.add("A");
8 lista.add("B");
9 lista.add("C");
10
11 Iterator<String> it = lista.iterator();
12 while (it.hasNext()) {
13 String valor = it.next();
14 System.out.println(valor);
15
16 if ("B".equals(valor)) {
17 lista.add("D");
18 }
19 }
20
21 System.out.println("Lista final: " + lista);
22 }
23 }
Essa abordagem garante que a iteração não seja afetada pelas modificações feitas durante o processo, permitindo uma maior segurança e consistência quando múltiplas threads estão acessando esse tipo de estrutura simultaneamente.
Abstração
A AbstractCollection
é uma abstract class
que oferece uma implementação parcial da interface Collection, fornecendo implementações básicas para métodos como add()
, remove()
, e clear()
. Ao adotar esta classe, as subclasses não precisam implementar todos os métodos da interface
Collection, porém podem se concentrar na implementação de comportamentos específicos, como o método iterator()
.
public abstract class AbstractCollection<E> extends Object implements Collection<E> { }
Para herdar esses métodos e ter um conjunto de características personalizados além dos fundamentais, basta criar uma classe com o seguinte:
public class MinhaColecao extends AbstractCollection<String> { }
Isso é particularmente útil quando se deseja criar uma coleção personalizada sem a necessidade de definir toda a funcionalidade da interface, já que a AbstractCollection
lida com os comportamentos mais comuns, simplificando o processo de implementação, tornando-se vantajoso quando a coleção personalizada compartilha características com as coleções existentes, mas precisa de uma implementação especializada para métodos específicos.
É de certa forma o conjunto de características mais gerais possíveis, contendo apenas o fundamental de seu funcionamento.
Exemplos pós Java 2
BlockingQueue
, uma extensão da Queue que foi introduzida no Java 5, que suporta operações de espera para adicionar e remover elementos quando a fila está cheia ou vazia. Exemplos de classes concretas que utilizam a implementação da BlockingQueue: LinkedBlockingQueue
, ArrayBlockingQueue
, PriorityBlockingQueue, DelayQueue, SynchronousQueue.
TransferQueue
que é uma extensão também da BlockingQueue
foi introduzida no Java 7 e sua principal implementação é na classe concreta LinkedTransferQueue
.
Complexidade computacional
Dentro da subinterface List
, dois dos tipos mais usados são ArrayList
e LinkedList
. Embora ambos implementem a interface List
e possuam a mesma finalidade básica, eles diferem em suas estruturas internas e, por consequência, em sua performance em operações específicas:
ArrayList
: Baseada em um array dinâmico, ela é otimizada para acesso rápido a elementos através de índices. O acesso a um elemento é O(1)
tornando-a ideal para cenários onde se precisa acessar frequentemente os dados pelo índice.
LinkedList
: Utiliza uma estrutura de lista duplamente encadeada, onde cada elemento está conectado ao anterior e ao próximo. Isso torna as operações de inserção e remoção em qualquer posição eficientes O(1)
.
Isso significa que para casos onde há um frequente acesso por índice ArrayList
é mais recomendado enquanto que num cenário onde se necessita de muitas operações de inserção e remoção a LinkedList
é a estrutura certa pra isso.
Conclusão
A API de Collection é uma estrutura robusta que oferece uma maneira padronizada de trabalhar com grupos de objetos, proporcionando abstrações, flexibilidade e eficiência ao lidar com dados. Ela não somente facilita a manipulação de dados como também oferece um conjunto poderoso e flexível de soluções que podem ser moldadas para diferentes cenários de aplicações, como viso no decorrer do artigo.