Set Interface
java.util.Set is the interface for collections that contain no duplicate elements. It models the mathematical idea of a set: an element is either in it or not, and adding something already present changes nothing. HashSet, LinkedHashSet, TreeSet, EnumSet, and the immutable sets from Set.of all implement it. This lesson looks at what the interface guarantees, how its implementations differ, and how to use sets for membership checks and set algebra.
What the Set Contract Guarantees
| Property | Meaning |
|---|---|
| No duplicates | At most one element e for which e.equals(x) is true (or, for sorted sets, compare(e, x) == 0) |
At most one null | If the implementation allows null at all |
| No index | There is no get(int); elements are accessed by membership or iteration |
| Order | Not part of the base contract: depends on the implementation |
Set adds no new methods to Collection. What it changes is the meaning of existing ones:
add(e)returnsfalseand leaves the set unchanged ifeis already present.equalsreturnstruefor any two sets containing the same elements, regardless of implementation or order.hashCodeis the sum of the elements' hash codes, so equal sets have equal hash codes.
Set<Integer> a = Set.of(1, 2, 3);
Set<Integer> b = new TreeSet<>(List.of(3, 2, 1));
System.out.println(a.equals(b)); // true: same elements, order and class irrelevant
The Set Family
Collection
└── Set
├── HashSet hash table, no order
│ └── LinkedHashSet + insertion order (SequencedSet, Java 21)
├── SortedSet
│ └── NavigableSet
│ └── TreeSet red-black tree, sorted
├── EnumSet bit vector for enum constants
└── CopyOnWriteArraySet, ConcurrentSkipListSet (concurrent)
| Implementation | Order | add / contains | Notes |
|---|---|---|---|
HashSet | None guaranteed | O(1) average | General default |
LinkedHashSet | Insertion order | O(1) average | Predictable output; deduplicate while keeping order |
TreeSet | Sorted | O(log n) | first, floor, ranges; needs ordering |
EnumSet | Enum declaration order | O(1) | Extremely compact and fast; enum elements only |
Set.of(...) | Unspecified | O(1) | Immutable; no null; duplicates in arguments throw |
ConcurrentSkipListSet | Sorted | O(log n) | Thread-safe sorted set |
Programming to the Set Interface
Set<String> tags = new HashSet<>();
public boolean hasAll(Set<String> granted, Set<String> required) {
return granted.containsAll(required);
}
As with List, declare variables and parameters as Set so the implementation can change. Use SortedSet or NavigableSet as the declared type when the code depends on ordering methods such as first() or ceiling(), and SequencedSet (Java 21) when it depends on a defined encounter order with getFirst(), getLast(), or reversed().
Membership: The Core Use Case
The reason to use a set is fast, duplicate-free membership:
Set<String> blocked = Set.of("spam@example.com", "bot@example.com");
if (blocked.contains(email)) {
reject();
}
With a List, contains scans every element; with a hash set, it examines one bucket. For checks repeated thousands of times, such as filtering a large data stream against a blocklist, the difference is enormous.
Set Algebra
The bulk methods inherited from Collection perform the classic set operations. They modify the set they are called on, so copy first when the original must stay unchanged:
| Mathematical operation | Code | Result |
|---|---|---|
| Union A ∪ B | a.addAll(b) | Elements in either |
| Intersection A ∩ B | a.retainAll(b) | Elements in both |
| Difference A − B | a.removeAll(b) | Elements in A but not B |
| Subset B ⊆ A | a.containsAll(b) | true if every element of B is in A |
| Disjoint | Collections.disjoint(a, b) | true if they share no elements |
Set<String> javaStudents = Set.of("ada", "grace", "alan");
Set<String> sqlStudents = Set.of("grace", "linus");
Set<String> both = new HashSet<>(javaStudents);
both.retainAll(sqlStudents); // [grace]
Set<String> eitherCourse = new HashSet<>(javaStudents);
eitherCourse.addAll(sqlStudents); // ada, grace, alan, linus
Set<String> javaOnly = new HashSet<>(javaStudents);
javaOnly.removeAll(sqlStudents); // ada, alan
boolean noOverlap = Collections.disjoint(javaStudents, Set.of("linus")); // true
Immutable Sets
Set<String> weekend = Set.of("SAT", "SUN");
Set<String> snapshot = Set.copyOf(mutableSet);
Set.of and Set.copyOf create unmodifiable sets that:
- throw
UnsupportedOperationExceptiononadd,remove, orclear, - reject
nullelements withNullPointerException, - (for
Set.of) throwIllegalArgumentExceptionif the arguments contain duplicates, - have no defined iteration order, which may even vary between runs of the program.
Set.copyOf silently drops duplicates, so it is the right way to make an immutable set from data that may repeat. Collections.unmodifiableSet(set) instead returns a read-only view that reflects later changes to the underlying set.
EnumSet
When the elements are constants of a single enum, EnumSet is the best implementation. It stores membership as bits in a long, making every operation fast and memory use tiny:
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
Set<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
Set<Day> weekdays = EnumSet.complementOf(EnumSet.copyOf(weekend));
Set<Day> midweek = EnumSet.range(Day.TUE, Day.THU);
Set<Day> none = EnumSet.noneOf(Day.class);
Set<Day> all = EnumSet.allOf(Day.class);
EnumSet iterates in the enum's declaration order and is the modern replacement for integer bit flags (READ | WRITE).
Requirements on Elements
The set implementation determines how duplicates are detected:
- Hash-based sets (
HashSet,LinkedHashSet) usehashCodeandequals. Elements must implement both consistently. - Sorted sets (
TreeSet) usecompareToor aComparator. Elements that compare as 0 are duplicates, even ifequalssays otherwise. - In every case, elements should be immutable, or at least must not change in ways that affect equality, hashing, or ordering while they are in the set.
Records, strings, wrapper types, enums, and java.time types all meet these requirements out of the box.
Converting Between Lists and Sets
List<String> withDupes = List.of("b", "a", "b", "c", "a");
Set<String> unique = new HashSet<>(withDupes); // no order
Set<String> uniqueOrdered = new LinkedHashSet<>(withDupes); // [b, a, c]
Set<String> uniqueSorted = new TreeSet<>(withDupes); // [a, b, c]
List<String> backToList = new ArrayList<>(uniqueOrdered); // [b, a, c]
List<String> viaStream = withDupes.stream().distinct().toList(); // [b, a, c]
stream().distinct() also keeps the first occurrence of each element in encounter order.
Choosing a Set
| Need | Use |
|---|---|
| Fast membership, order irrelevant | HashSet |
| Remove duplicates but keep first-seen order | LinkedHashSet |
| Sorted iteration, nearest-value or range queries | TreeSet |
| Elements are enum constants | EnumSet |
| Fixed set of constants or a returned snapshot | Set.of / Set.copyOf |
| Shared between threads | ConcurrentHashMap.newKeySet(), ConcurrentSkipListSet |
Practical Example: Role-Based Permissions
Users can hold several roles, and each role grants a set of permissions. A user's effective permissions are the union of their roles' permissions; an action is allowed only if the user holds every permission it requires.
import java.util.*;
public class Permissions {
enum Perm { READ, WRITE, DELETE, PUBLISH, MANAGE_USERS }
static final Map<String, Set<Perm>> ROLES = Map.of(
"viewer", EnumSet.of(Perm.READ),
"editor", EnumSet.of(Perm.READ, Perm.WRITE, Perm.PUBLISH),
"admin", EnumSet.allOf(Perm.class)
);
static Set<Perm> effective(Collection<String> roles) {
Set<Perm> result = EnumSet.noneOf(Perm.class);
for (String r : roles) {
result.addAll(ROLES.getOrDefault(r, Set.of()));
}
return result;
}
public static void main(String[] args) {
Set<Perm> user = effective(List.of("viewer", "editor"));
System.out.println("Effective: " + user);
Set<Perm> needed = EnumSet.of(Perm.WRITE, Perm.DELETE);
System.out.println("Can do all? " + user.containsAll(needed));
Set<Perm> missing = EnumSet.copyOf(needed);
missing.removeAll(user);
System.out.println("Missing: " + missing);
Set<Perm> adminOnly = EnumSet.complementOf(EnumSet.copyOf(user));
System.out.println("Admin-only: " + adminOnly);
System.out.println("Same as editor? " + user.equals(ROLES.get("editor")));
}
}
Output:
Effective: [READ, WRITE, PUBLISH]
Can do all? false
Missing: [DELETE]
Admin-only: [DELETE, MANAGE_USERS]
Same as editor? true
Every operation here is a set operation. addAll computes the union of the user's roles, and because sets ignore duplicates, READ appears once even though two roles grant it. containsAll is the subset test that decides whether an action is allowed, and removeAll on a copy computes exactly which permissions are missing, which makes for useful error messages. EnumSet keeps the output in the enum's declaration order and makes each operation a fast bitwise computation. The final line shows the Set equality contract: the computed set equals the editor role's set because they contain the same elements, regardless of how each was built.