Interfaz Set de Java
Colecciones de elementos únicos en Java con la interfaz Set y sus implementaciones principales.
Set<E> es una Collection<E> con una regla adicional: sin duplicados. La interfaz en sí apenas añade métodos nuevos — hereda add, remove, contains, size, iterator y el resto. Lo que cambia es lo que esos métodos garantizan: add(e) devuelve false (y no modifica nada) si ya existe un elemento igual en el conjunto; dos conjuntos son equals si contienen los mismos elementos independientemente del orden; y hashCode es la suma de los hashes de los elementos, de modo que conjuntos iguales siempre coinciden.
Ese pequeño contrato — "los elementos son únicos" — resulta ser exactamente lo que se necesita para pruebas de pertenencia, deduplicación, intersección/unión y miles de patrones que se expresan mal con una List.
Qué cuenta como duplicado
Un Set decide la duplicación mediante equals (y, en conjuntos basados en hash, hashCode como función de agrupación). No es igualdad de referencias, ni "se ve igual cuando se imprime". Si introduces tu propia clase en un Set debes sobrescribir ambos métodos juntos; el capítulo sobre equals y hashCode explicó el contrato.
Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("java"); // add returns false; the set still has one element
tags.add(new String("java")); // also false — equals, not referenceUna trampa común: almacenar elementos mutables y luego mutarlos mientras viven en un conjunto basado en hash. El hash del elemento cambia, el conjunto lo busca en el cubo equivocado y contains empieza a mentir. La regla: si introduces un objeto en un hash set, trátalo como efectivamente inmutable desde ese momento. TreeSet tiene la misma trampa con compareTo.
Las cuatro implementaciones estándar
java.util incluye cuatro implementaciones de Set que cubren casi todos los casos de uso:
| Clase | Estructura interna | Orden | Elementos null | Uso típico |
|---|---|---|---|---|
HashSet | tabla hash | ninguno | un null permitido | la opción predeterminada — pruebas de pertenencia más rápidas |
LinkedHashSet | tabla hash + lista doblemente enlazada | orden de inserción | un null permitido | cuando el orden de iteración debe coincidir con el de inserción |
TreeSet | árbol rojo-negro | orden natural / por comparador | sin null | cuando se necesita iteración ordenada o consultas por rango |
EnumSet | vector de bits | orden de declaración del enum | sin null | un Set<MyEnum> — pequeño, rápido y ordenado |
Las tres primeras se cubren en los tres capítulos siguientes; EnumSet se trata más adelante en el libro junto con EnumMap.
Las operaciones masivas: unión, intersección, diferencia
Un Set hereda cuatro operaciones masivas de Collection, y en un Set adquieren el significado de la teoría de conjuntos:
addAll(other)— unión (en el lugar). Tras la llamada,acontiene todos los elementos de ambos lados.retainAll(other)— intersección (en el lugar). Tras la llamada,acontiene solo los elementos que también estaban enother.removeAll(other)— diferencia (en el lugar). Tras la llamada,acontiene solo los elementos que no estaban enother.containsAll(other)— prueba de subconjunto. Devuelvetruesi cada elemento deotherestá ena.
Estas operaciones modifican el receptor. Si necesitas una versión no destructiva, copia primero: Set<E> u = new HashSet<>(a); u.addAll(b);.
Igualdad y códigos hash de los conjuntos
El contrato de Set para equals es inusual: dos conjuntos son iguales si contienen los mismos elementos, independientemente del orden o del tipo de implementación. Un HashSet, un LinkedHashSet y un TreeSet con los mismos elementos se comparan como iguales entre sí.
Set<Integer> a = new HashSet<>(List.of(1, 2, 3));
Set<Integer> b = new TreeSet<>(List.of(3, 2, 1));
System.out.println(a.equals(b)); // truePor eso las operaciones masivas y los métodos de fábrica inmutables pueden moverse entre implementaciones libremente — solo se respeta la regla "mismos elementos".
Fábricas de solo lectura e inmutables
Desde Java 9 la forma más sencilla de crear un conjunto pequeño y fijo es Set.of(...):
Set<String> primary = Set.of("red", "green", "blue");Set.of devuelve un conjunto inmutable que rechaza elementos null y lanza una excepción si se pasa un duplicado en el momento de la construcción. Para una instantánea defensiva de un conjunto existente, usa Set.copyOf(existing) — también inmutable, también rechaza nulls.
Para una vista que oculte las mutaciones (el original puede seguir modificándose, pero los llamantes no pueden añadir a través de la vista) usa Collections.unmodifiableSet(s). El capítulo sobre colecciones no modificables más adelante en esta parte explica cuándo elegir cada opción.
Un ejemplo práctico: deduplicación, álgebra de conjuntos y orden
El programa siguiente usa las cuatro implementaciones, demuestra las operaciones masivas con datos reales y muestra cómo difiere el orden de iteración entre HashSet, LinkedHashSet y TreeSet.
Qué destacar de la ejecución:
adddevolviófalsepara cada\"java\"duplicado — así se escribe un deduplicador en dos líneas.- Unión, intersección y diferencia son un
addAll/retainAll/removeAllcada una — copia primero si no quieres perder el original. - Las tres implementaciones contienen los mismos elementos y se comparan como iguales, pero iteran en órdenes muy distintos. Elige la implementación según el orden que necesites, no por inercia.
Qué sigue
La implementación de Set más común — y a la que se recurre salvo que haya una razón para no hacerlo — está respaldada por una tabla hash. HashSet es el siguiente; cubriremos el factor de carga, el rehash y qué significa "tiempo casi constante" en la práctica.