Tres formas de implementar un conjunto
La interfaz Set<E> garantiza unicidad de elementos (no hay duplicados), pero no especifica cómo se almacenan internamente. El JDK ofrece tres implementaciones principales, cada una con compromisos distintos:
| Clase | Orden | Complejidad básica |
|-------|-------|--------------------|
| HashSet | Ninguno (según hash) | O(1) operaciones |
| LinkedHashSet | Inserción | O(1) operaciones |
| TreeSet | Natural o Comparator | O(log n) operaciones |
HashSet: el más rápido, sin orden
HashSet es la implementación por defecto. Usa una tabla hash interna (HashMap por debajo). Ideal cuando solo te importa la pertenencia y el orden es irrelevante.
Set<String> visitados = new HashSet<>();
visitados.add("home");
visitados.add("about");
visitados.add("home"); // ignorado, ya existía
System.out.println(visitados.size()); // 2
Características:
- No garantiza orden (y el orden puede cambiar entre ejecuciones).
- Permite un único
null. - No thread-safe.
- Complejidad O(1) en
add,remove,containssi el hash es bueno.
Usa HashSet cuando: el orden no importa y quieres máxima velocidad.
LinkedHashSet: orden de inserción con costes de HashSet
LinkedHashSet extiende HashSet añadiendo una lista doblemente enlazada que mantiene el orden de inserción. El resultado: misma complejidad O(1), con el extra de preservar orden.
Set<String> orden = new LinkedHashSet<>();
orden.add("tercero");
orden.add("primero");
orden.add("segundo");
for (String s : orden) {
System.out.println(s); // tercero, primero, segundo
}
Ventajas sobre HashSet:
- Iteración predecible en el mismo orden que las inserciones.
- Útil cuando la iteración ordenada importa pero no quieres pagar O(log n) de
TreeSet.
Coste ligeramente superior a HashSet en memoria (dos punteros por elemento), pero operaciones siguen siendo O(1).
Implementa SequencedSet (Java 21+)
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("b");
set.add("c");
set.addFirst("a"); // Java 21+
set.addLast("d");
System.out.println(set); // [a, b, c, d]
String primero = set.getFirst(); // "a"
String ultimo = set.getLast(); // "d"
TreeSet: orden natural o por comparator
TreeSet mantiene los elementos ordenados según el orden natural (Comparable) o un Comparator explícito. Internamente usa un árbol binario balanceado (red-black tree).
Set<Integer> ordenado = new TreeSet<>();
ordenado.add(5);
ordenado.add(1);
ordenado.add(3);
ordenado.add(2);
System.out.println(ordenado); // [1, 2, 3, 5]
// Con Comparator
Set<Persona> porEdad = new TreeSet<>(Comparator.comparingInt(Persona::edad));
Complejidad O(log n) en operaciones: más lento que HashSet pero ofrece:
API NavigableSet
TreeSet implementa NavigableSet (extensión de SortedSet), que aporta métodos utiles:
| Método | Qué hace |
|--------|----------|
| first() / last() | Menor / mayor |
| ceiling(e) | Menor elemento ≥ e |
| floor(e) | Mayor elemento ≤ e |
| higher(e) | Menor estrictamente > e |
| lower(e) | Mayor estrictamente < e |
| headSet(e) | Elementos < e |
| tailSet(e) | Elementos ≥ e |
| subSet(from, to) | Rango [from, to) |
| descendingSet() | Vista invertida |
| pollFirst() / pollLast() | Extraer y devolver |
NavigableSet<Integer> puntos = new TreeSet<>(List.of(10, 20, 30, 40, 50));
puntos.ceiling(25); // 30 (menor ≥ 25)
puntos.floor(25); // 20 (mayor ≤ 25)
puntos.subSet(20, 40); // [20, 30]
puntos.descendingSet(); // [50, 40, 30, 20, 10]
Ideal para rangos, búsquedas ordenadas, leaderboards, etc.
Null en TreeSet
TreeSet no permite null si usas orden natural (produce NPE al comparar). Con un Comparator explícito, sí podría aceptarlos (dependiendo del comparador).
EnumSet: óptimo para claves enum
Ya visto en el tutorial de enums, pero imprescindible recordar: si tus elementos son de un enum, usa EnumSet:
EnumSet<DayOfWeek> laborales = EnumSet.range(DayOfWeek.MONDAY, DayOfWeek.FRIDAY);
EnumSet<DayOfWeek> finSemana = EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
Es mucho más eficiente que HashSet<DayOfWeek> (bitmap interno).
Thread-safety
Ninguna de estas clases es thread-safe. Alternativas:
Collections.synchronizedSet(new HashSet<>()): sincronización externa.ConcurrentHashMap.newKeySet(): set thread-safe basado enConcurrentHashMap.CopyOnWriteArraySet: para lecturas masivas y pocas modificaciones.
Cómo elegir
| Necesitas... | Usa |
|--------------|-----|
| Máxima velocidad, orden irrelevante | HashSet |
| Preservar orden de inserción | LinkedHashSet |
| Orden natural u ordenado por criterio | TreeSet |
| Búsquedas por rango o vecinos | TreeSet (NavigableSet) |
| Claves de un enum | EnumSet |
| Thread-safe, lecturas frecuentes | CopyOnWriteArraySet |
| Thread-safe concurrente | ConcurrentHashMap.newKeySet() |
| Inmutable (creación literal) | Set.of(...) |
Buenas prácticas
- El tipo declarado suele ser
Set<E>(interfaz); la implementación se elige en la asignación. - Iterar
HashSetno es reproducible: si lo necesitas, usaLinkedHashSet. TreeSetes más lento pero es la única opción para operaciones ordenadas y de rango.- Para datos inmutables pequeños,
Set.of(...)es lo más eficiente. - Asegúrate de que las clases usadas como elementos tienen
equalsyhashCodecoherentes (oComparable/ComparatorparaTreeSet).
Resumen
HashSet, LinkedHashSet y TreeSet cubren tres necesidades distintas: velocidad pura, orden de inserción, y orden por criterio. Conocer sus compromisos permite elegir la estructura correcta sin dejarlo al azar: diferencia clásica entre código que funciona y código bien diseñado.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Java
Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Elegir entre HashSet, LinkedHashSet y TreeSet según la necesidad. Usar LinkedHashSet para preservar orden de inserción con costes HashSet. Aprovechar TreeSet para conjuntos ordenados (API NavigableSet). Entender la complejidad de cada operación. Combinar con SequencedSet (Java 21+). Usar EnumSet para claves enum.