W3docs

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 reference

Una 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:

ClaseEstructura internaOrdenElementos nullUso típico
HashSettabla hashningunoun null permitidola opción predeterminada — pruebas de pertenencia más rápidas
LinkedHashSettabla hash + lista doblemente enlazadaorden de inserciónun null permitidocuando el orden de iteración debe coincidir con el de inserción
TreeSetárbol rojo-negroorden natural / por comparadorsin nullcuando se necesita iteración ordenada o consultas por rango
EnumSetvector de bitsorden de declaración del enumsin nullun 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, a contiene todos los elementos de ambos lados.
  • retainAll(other)intersección (en el lugar). Tras la llamada, a contiene solo los elementos que también estaban en other.
  • removeAll(other)diferencia (en el lugar). Tras la llamada, a contiene solo los elementos que no estaban en other.
  • containsAll(other)prueba de subconjunto. Devuelve true si cada elemento de other está en a.

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));   // true

Por 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.

java— editable, runs on the server

Qué destacar de la ejecución:

  • add devolvió false para cada \"java\" duplicado — así se escribe un deduplicador en dos líneas.
  • Unión, intersección y diferencia son un addAll/retainAll/removeAll cada 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.

Práctica

Práctica
¿Qué devuelve `set.add(x)` cuando `x` ya es un elemento de `set`, según el contrato de `Set`?
¿Qué devuelve `set.add(x)` cuando `x` ya es un elemento de `set`, según el contrato de `Set`?
Was this page helpful?