Introducción
Más allá de ArrayList y HashMap, Java proporciona muchos tipos de colecciones especializadas. En este capítulo aprenderás sobre Set, Queue, Deque e implementaciones avanzadas de Map.
Set - Elementos Únicos
Un Set almacena elementos únicos sin duplicados.
import java.util.*;
// HashSet - sin orden, más rápido
Set<String> hashSet = new HashSet<>();
hashSet.add("Java");
hashSet.add("Kotlin");
hashSet.add("Java"); // Ignorado - duplicado
System.out.println(hashSet.size()); // 2
// LinkedHashSet - mantiene orden de inserción
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("C");
linkedSet.add("A");
linkedSet.add("B");
// Itera en orden: C, A, B
// TreeSet - orden ordenado
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(1);
treeSet.add(3);
// Itera en orden: 1, 3, 5
Queue - Operaciones FIFO
Las colas procesan elementos en orden First-In-First-Out (primero en entrar, primero en salir).
// LinkedList como Queue
Queue<String> cola = new LinkedList<>();
cola.offer("Primero");
cola.offer("Segundo");
cola.offer("Tercero");
System.out.println(cola.poll()); // "Primero"
System.out.println(cola.peek()); // "Segundo" (no lo elimina)
System.out.println(cola.poll()); // "Segundo"
// PriorityQueue - ordena por prioridad
Queue<Integer> colaPrioridad = new PriorityQueue<>();
colaPrioridad.offer(5);
colaPrioridad.offer(1);
colaPrioridad.offer(3);
System.out.println(colaPrioridad.poll()); // 1 (el menor primero)
System.out.println(colaPrioridad.poll()); // 3
Deque - Cola de Doble Extremo
Deque permite agregar/eliminar desde ambos extremos.
Deque<String> deque = new ArrayDeque<>();
// Agregar a ambos extremos
deque.addFirst("A");
deque.addLast("C");
deque.addFirst("B");
// Orden: B, A, C
// Eliminar desde ambos extremos
deque.removeFirst(); // "B"
deque.removeLast(); // "C"
// Restante: A
LinkedHashMap - Mapa Ordenado
Mantiene el orden de inserción:
Map<String, Integer> linkedMap = new LinkedHashMap<>();
linkedMap.put("Carlos", 30);
linkedMap.put("Ana", 25);
linkedMap.put("Bob", 28);
// Itera en orden de inserción: Carlos, Ana, Bob
for (String clave : linkedMap.keySet()) {
System.out.println(clave + ": " + linkedMap.get(clave));
}
TreeMap - Mapa Ordenado
Las claves se ordenan automáticamente:
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Carlos", 30);
treeMap.put("Ana", 25);
treeMap.put("Bob", 28);
// Itera en orden alfabético: Ana, Bob, Carlos
for (String clave : treeMap.keySet()) {
System.out.println(clave + ": " + treeMap.get(clave));
}
Comparación de Rendimiento
| Colección | Agregar | Contiene | Iterar | Ordenado |
|---|---|---|---|---|
| HashSet | O(1) | O(1) | O(n) | No |
| LinkedHashSet | O(1) | O(1) | O(n) | Inserción |
| TreeSet | O(log n) | O(log n) | O(n) | Ordenado |
| HashMap | O(1) | O(1) | O(n) | No |
| LinkedHashMap | O(1) | O(1) | O(n) | Inserción |
| TreeMap | O(log n) | O(log n) | O(n) | Ordenado |
Buenas Prácticas
- Usa HashSet para búsquedas rápidas sin orden
- Usa LinkedHashSet cuando el orden de inserción importa
- Usa TreeSet para ordenamiento automático
- Usa HashMap para almacenamiento general clave-valor
- Usa LinkedHashMap para mantener orden de inserción
- Usa TreeMap para claves ordenadas
- Elige Queue para procesamiento FIFO
- Elige Deque para operaciones de pila o doble extremo
Ejemplos Prácticos
Eliminar duplicados de una lista:
List<String> listaConDuplicados = Arrays.asList("A", "B", "A", "C", "B");
Set<String> conjuntoUnico = new HashSet<>(listaConDuplicados);
List<String> listaUnica = new ArrayList<>(conjuntoUnico);
Programación de tareas con prioridad:
Queue<Tarea> colaTareas = new PriorityQueue<>(
Comparator.comparingInt(Tarea::getPrioridad)
);
colaTareas.offer(new Tarea("Prioridad baja", 3));
colaTareas.offer(new Tarea("Prioridad alta", 1));
colaTareas.offer(new Tarea("Prioridad media", 2));
while (!colaTareas.isEmpty()) {
Tarea tarea = colaTareas.poll();
tarea.ejecutar();
}
Caché LRU con LinkedHashMap:
public class CacheLRU<K, V> extends LinkedHashMap<K, V> {
private final int capacidad;
public CacheLRU(int capacidad) {
super(capacidad, 0.75f, true); // accessOrder = true
this.capacidad = capacidad;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacidad;
}
}
Conclusión
Comprender diferentes tipos de colecciones y sus características de rendimiento te ayuda a elegir la estructura de datos correcta para cada situación, conduciendo a un código más eficiente.
En el próximo capítulo, exploraremos Streams y expresiones Lambda.