Viktor's Tech Lab

Blog pessoal

Collection API: definições, abstrações, estruturas fail-test/fail-safe e complexidade computacional

Categorias = [ Java ]

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.

1import java.util.ArrayList;
2import java.util.Iterator;
3import java.util.List;
4
5public 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.

1import java.util.Iterator;
2import java.util.concurrent.CopyOnWriteArrayList;
3
4public 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.