Estruturas de Dados em Java
Domine as estruturas fundamentais para organizar e manipular dados eficientemente
1. O que são Estruturas de Dados?
Estruturas de dados são formas de organizar e armazenar dados na memória do computador. Cada estrutura possui características únicas que a tornam ideal para diferentes situações.
Uma boa escolha de estrutura de dados pode tornar seu programa mais eficiente, rápido e fácil de manter. Usar a estrutura errada pode resultar em performance ruim e código complexo.
Em Java, as principais estruturas estão no pacote java.util, conhecidas como Collections Framework.
2. Arrays - Estrutura Fixa
Um array é uma coleção de elementos do mesmo tipo armazenados em posições contíguas da memória.
Características:
- Tamanho fixo - definido na criação
- Acesso rápido - O(1) por índice
- Tipado - todos os elementos do mesmo tipo
- Alocação contígua - em memória sequencial
Exemplos Práticos:
// Array de inteiros
int[] numeros = new int[5]; // Cria array com 5 posições
numeros[0] = 10;
numeros[1] = 20;
numeros[2] = 30;
// Array inicializado
int[] notas = {7, 8, 9, 10, 6};
// Percorrer array
for (int i = 0; i < notas.length; i++) {
System.out.println("Nota: " + notas[i]);
}
// Array de Strings
String[] nomes = {"João", "Maria", "Pedro"};
System.out.println(nomes[0]); // João
// Array bidimensional (matriz)
int[][] matriz = new int[3][3];
matriz[0][0] = 1;
matriz[0][1] = 2;
matriz[0][2] = 3;
// Acessar matriz
System.out.println(matriz[0][1]); // 2
3. ArrayList - Dinâmico e Flexível
ArrayList é uma lista dinâmica que cresce automaticamente conforme você adiciona elementos.
Características:
- Tamanho dinâmico - cresce automaticamente
- Acesso rápido - O(1) por índice
- Generics - tipagem de elementos
- Métodos úteis - add(), remove(), get(), size()
Exemplo Completo:
import java.util.ArrayList;
// Criar ArrayList
ArrayList frutas = new ArrayList<>();
// Adicionar elementos
frutas.add("Maçã");
frutas.add("Banana");
frutas.add("Laranja");
System.out.println("Total de frutas: " + frutas.size()); // 3
// Acessar elemento
System.out.println(frutas.get(0)); // Maçã
// Adicionar em posição específica
frutas.add(1, "Morango");
// Remover elemento
frutas.remove("Banana");
frutas.remove(0); // Remove por índice
// Verificar se existe
if (frutas.contains("Laranja")) {
System.out.println("Laranja encontrada!");
}
// Percorrer com foreach
for (String fruta : frutas) {
System.out.println(fruta);
}
// Limpar lista
frutas.clear();
System.out.println("Lista vazia: " + frutas.isEmpty()); // true
ArrayList com Objetos:
// Classe Produto
public class Produto {
private String nome;
private double preco;
public Produto(String nome, double preco) {
this.nome = nome;
this.preco = preco;
}
public String getNome() { return nome; }
public double getPreco() { return preco; }
}
// Usar ArrayList com objetos
ArrayList carrinho = new ArrayList<>();
carrinho.add(new Produto("Notebook", 3000));
carrinho.add(new Produto("Mouse", 50));
carrinho.add(new Produto("Teclado", 150));
// Calcular total
double total = 0;
for (Produto p : carrinho) {
total += p.getPreco();
}
System.out.println("Total do carrinho: R$ " + total);
4. Outras Estruturas de Lista
LinkedList - Acesso Sequencial
LinkedList usa nós encadeados, melhor para inserções/remoções no meio. Acesso sequencial é mais lento.
import java.util.LinkedList;
LinkedList numeros = new LinkedList<>();
numeros.add(1);
numeros.add(2);
numeros.add(3);
// Adicionar no início
numeros.addFirst(0);
// Adicionar no final
numeros.addLast(4);
// Remover primeiro
numeros.removeFirst();
// Remover último
numeros.removeLast();
// Obter primeiro
int primeiro = numeros.getFirst();
// Obter último
int ultimo = numeros.getLast();
5. Queue (Fila) - FIFO
Queue (fila) segue o princípio FIFO (First In, First Out) - o primeiro a entrar é o primeiro a sair.
Principais Métodos:
- offer() ou add() - adiciona elemento
- poll() ou remove() - remove primeiro
- peek() ou element() - visualiza primeiro
- size() - tamanho da fila
Exemplo Prático:
import java.util.Queue;
import java.util.LinkedList;
// Criar fila
Queue fila = new LinkedList<>();
// Adicionar elementos
fila.offer("João");
fila.offer("Maria");
fila.offer("Pedro");
System.out.println("Tamanho: " + fila.size()); // 3
// Ver primeiro sem remover
System.out.println("Próximo: " + fila.peek()); // João
// Remover primeiro
String primeiro = fila.poll();
System.out.println("Atendido: " + primeiro); // João
// Processar fila
while (!fila.isEmpty()) {
String pessoa = fila.poll();
System.out.println("Atendendo: " + pessoa);
}
Simulação: Fila de Banco
Queue filaBanco = new LinkedList<>();
// Clientes chegando
filaBanco.offer("Cliente A");
filaBanco.offer("Cliente B");
filaBanco.offer("Cliente C");
// Atendimento
int caixa = 1;
while (!filaBanco.isEmpty()) {
String cliente = filaBanco.poll();
System.out.println("Caixa " + caixa + " atende: " + cliente);
}
6. Stack (Pilha) - LIFO
Stack (pilha) segue o princípio LIFO (Last In, First Out) - o último a entrar é o primeiro a sair.
Principais Métodos:
- push() - adiciona elemento no topo
- pop() - remove do topo
- peek() - visualiza topo
- empty() - verifica se vazia
Exemplo Prático:
import java.util.Stack;
// Criar pilha
Stack pilha = new Stack<>();
// Adicionar elementos
pilha.push(1);
pilha.push(2);
pilha.push(3);
pilha.push(4);
System.out.println("Topo: " + pilha.peek()); // 4
// Remover todos
while (!pilha.empty()) {
int valor = pilha.pop();
System.out.println("Removido: " + valor);
}
// Ordem: 4, 3, 2, 1
Aplicação Real: Desfazer (Undo)
// Simular ações em um editor de texto
Stack historico = new Stack<>();
// Ações do usuário
historico.push("Digitou: Hello");
historico.push("Digitou: World");
historico.push("Apagou: World");
System.out.println("Histórico:");
while (!historico.empty()) {
System.out.println("- " + historico.pop());
}
// Saída (em ordem reversa):
// - Apagou: World
// - Digitou: World
// - Digitou: Hello
7. Comparação de Estruturas
| Estrutura | Tipo | Acesso | Inserção | Remoção | Melhor Uso |
|---|---|---|---|---|---|
| Array | Fixo | O(1) | O(n) | O(n) | Dados imutáveis |
| ArrayList | Dinâmico | O(1) | O(n) | O(n) | Acesso frequente |
| LinkedList | Dinâmico | O(n) | O(1)* | O(1)* | Inserções no meio |
| Queue | FIFO | O(1) | O(1) | O(1) | Fila de espera |
| Stack | LIFO | O(1) | O(1) | O(1) | Histórico, backtrack |
* Com referência ao nó
8. Set e Map - Estruturas Especializadas
HashSet - Conjunto Único
HashSet armazena valores únicos sem duplicatas. Não mantém ordem.
import java.util.HashSet;
import java.util.Set;
Set cores = new HashSet<>();
cores.add("Vermelho");
cores.add("Azul");
cores.add("Verde");
cores.add("Vermelho"); // Não será adicionado (duplicata)
System.out.println("Total de cores: " + cores.size()); // 3
// Verificar se existe
if (cores.contains("Azul")) {
System.out.println("Azul existe!");
}
// Remover
cores.remove("Verde");
// Percorrer
for (String cor : cores) {
System.out.println(cor);
}
HashMap - Chave-Valor
HashMap armazena pares chave-valor. Ideal para buscas por chave.
import java.util.HashMap;
import java.util.Map;
// Criar mapa
Map notas = new HashMap<>();
// Adicionar pares
notas.put("João", 85);
notas.put("Maria", 92);
notas.put("Pedro", 78);
// Obter valor por chave
int nota = notas.get("João"); // 85
// Verificar se chave existe
if (notas.containsKey("Maria")) {
System.out.println("Nota de Maria: " + notas.get("Maria"));
}
// Verificar se valor existe
if (notas.containsValue(78)) {
System.out.println("Existe alguém com nota 78");
}
// Iterar sobre pares
for (String aluno : notas.keySet()) {
System.out.println(aluno + ": " + notas.get(aluno));
}
// Remover
notas.remove("Pedro");
// Tamanho
System.out.println("Total de alunos: " + notas.size());
9. Boas Práticas com Estruturas de Dados
✓ FAÇA:
- Escolha a estrutura certa para o problema
- Use generics para segurança de tipo
- Verifique tamanho antes de acessar
- Use for-each quando possível
- Considere performance e memória
- Documente por que escolheu cada estrutura
✗ EVITE:
- Usar ArrayList para tudo (nem sempre é a melhor)
- Acessar índices sem verificar limites
- Modificar coleções enquanto itera
- Esquecer de verificar null
- Usar estruturas inadequadas por preguiça
10. Exemplo Prático: Sistema de Gerenciamento
import java.util.*;
public class SistemaEstoque {
private Map produtos;
private Queue pedidos;
private Stack historico;
public SistemaEstoque() {
this.produtos = new HashMap<>();
this.pedidos = new LinkedList<>();
this.historico = new Stack<>();
}
public void adicionarProduto(int id, String nome, double preco) {
produtos.put(id, new Produto(id, nome, preco));
historico.push("Produto adicionado: " + nome);
}
public void fazerPedido(String cliente, int idProduto) {
if (produtos.containsKey(idProduto)) {
Produto p = produtos.get(idProduto);
pedidos.offer(new Pedido(cliente, p));
historico.push("Pedido de " + cliente + " para " + p.getNome());
}
}
public void processarPedidos() {
while (!pedidos.isEmpty()) {
Pedido p = pedidos.poll();
System.out.println("Processando: " + p);
}
}
public void exibirHistorico() {
Stack temp = (Stack) historico.clone();
System.out.println("=== Histórico de Operações ===");
while (!temp.empty()) {
System.out.println("- " + temp.pop());
}
}
}
class Produto {
private int id;
private String nome;
private double preco;
public Produto(int id, String nome, double preco) {
this.id = id;
this.nome = nome;
this.preco = preco;
}
public String getNome() { return nome; }
public double getPreco() { return preco; }
}
class Pedido {
private String cliente;
private Produto produto;
public Pedido(String cliente, Produto produto) {
this.cliente = cliente;
this.produto = produto;
}
@Override
public String toString() {
return cliente + " pediu " + produto.getNome() +
" (R$ " + produto.getPreco() + ")";
}
}
// Uso
SistemaEstoque sistema = new SistemaEstoque();
sistema.adicionarProduto(1, "Notebook", 3000);
sistema.adicionarProduto(2, "Mouse", 50);
sistema.fazerPedido("João", 1);
sistema.fazerPedido("Maria", 2);
sistema.processarPedidos();
sistema.exibirHistorico();
11. Exercícios Práticos
Exercício 1: Inverter Lista
Crie uma função que receba um ArrayList e retorne uma nova lista com os elementos em ordem inversa.
Exercício 2: Validar Parênteses
Use uma Stack para validar se os parênteses de uma expressão estão balanceados.
Exercício 3: Remover Duplicatas
Crie uma função que receba uma lista com duplicatas e retorne um HashSet com valores únicos.
Exercício 4: Fila de Atendimento
Implemente um sistema de fila onde clientes entram, são atendidos e removidos da fila.
Exercício 5: Sistema de Cachê
Use HashMap para criar um sistema que armazene dados frequentes para acesso rápido.
12. Resumo - Estruturas Essenciais
📋 Array
Coleção fixa e tipada. Rápida para acesso, lenta para inserção/remoção.
📝 ArrayList
Lista dinâmica. Melhor para uso geral quando precisa flexibilidade.
🔗 LinkedList
Lista encadeada. Melhor para inserções/remoções frequentes no meio.
📞 Queue (Fila)
FIFO. Use para atendimento, processamento sequencial.
📚 Stack (Pilha)
LIFO. Use para histórico, backtracking, expressões.
🎯 Set
Valores únicos. Use para remover duplicatas, validações.
🗂️ Map
Chave-valor. Use para buscas rápidas, mapeamentos.
Conclusão
Dominar estruturas de dados é essencial para todo programador. A escolha correta pode fazer a diferença entre um programa eficiente e um programa que trava.
Cada estrutura tem seu propósito: Arrays para dados fixos, ArrayList para flexibilidade, Queue para FIFO, Stack para LIFO, HashMap para buscas rápidas.
Pratique com exemplos reais e você dominará estruturas de dados em Java! 🚀