Collections Framework: The Set Interface, Uniqueness & Immutability
Key Takeaways
A Set is an unordered collection that contains no duplicate elements, modeling the mathematical set abstraction. Because Set is an interface, it cannot be instantiated directly with new Set(); instead, developers use concrete implementations such as HashSet, LinkedHashSet, or TreeSet. Adding duplicate elements to a Set does not trigger an error, but the duplicate is silently ignored and not stored. Developers can initialize sets dynamically via mutable concrete implementations or concisely via the static factory method Set.of(...), which produces an immutable set that forbids subsequent structural modifications.
- No Duplicates Allowed: A
Setguarantees uniqueness; adding an element that already exists returnsfalseinternally without throwing an exception or inserting a duplicate. - Unordered Nature: Standard implementations like
HashSetdo not preserve the insertion order of elements, and callers have no control over element positioning. - Core Collection Methods: Key operational methods include
add(element),contains(object),remove(object), andsize(). - Immutable Factory Method (
Set.of): Creates a pre-populated, unmodifiable set instance; invoking mutator methods such asadd()orremove()on an unmodifiable set causes a runtimeUnsupportedOperationException.
Main Discussion
Idiomatic Java Code Snippets
1. Mutable Set Operations Using HashSet
HashSet provides a mutable collection that enforces element uniqueness and supports dynamic querying and removal:
package collections_demo;
import java.util.HashSet;
import java.util.Set;
public class MutableSetDemo {
public static void main(String[] args) {
// Polymorphic declaration: Interface type holding concrete HashSet instance
Set<String> fruits = new HashSet<>();
// Adding elements to the set
fruits.add("apple");
fruits.add("banana");
fruits.add("lemon");
// Attempting to add a duplicate element: no error, but will not be stored
fruits.add("apple");
// Elements print without duplicates and without guaranteed insertion order
System.out.println("Current fruits set: " + fruits);
// Checking membership using contains()
boolean hasLemon = fruits.contains("lemon");
System.out.println("Have lemon? " + hasLemon); // Prints: true
// Removing an element from the set
fruits.remove("lemon");
// Querying set cardinality using size()
System.out.println("Number of elements after removing lemon: " + fruits.size()); // Prints: 2
System.out.println("Remaining fruits: " + fruits);
}
}
2. Immutable Sets Using the Static Factory Method Set.of
Set.of offers a concise way to instantiate pre-populated sets, but enforces strict runtime immutability:
package collections_demo;
import java.util.Set;
public class ImmutableSetDemo {
public static void main(String[] args) {
// Static factory method creates an immutable (unmodifiable) Set
Set<String> moreFruit = Set.of("pear", "raisins", "cherry");
System.out.println("More fruit: " + moreFruit);
// Attempting to modify an immutable set compiles cleanly,
// but fails at runtime with UnsupportedOperationException:
try {
moreFruit.add("cranberry");
} catch (UnsupportedOperationException e) {
System.out.println("Caught expected exception: Cannot call add() on an immutable set.");
}
try {
moreFruit.remove("pear");
} catch (UnsupportedOperationException e) {
System.out.println("Caught expected exception: Cannot call remove() on an immutable set.");
}
}
}
Under the Hood / JVM Mechanics
-
Instantiation Boundary: Because Set is an interface, invoking new Set() causes a compile-time failure. Instantiation must target a concrete class (e.g.,
new HashSet<>()) or utilize static factory helpers like Set.of(...). -
Duplicate Rejection Mechanism: When fruits.add('apple') is called, the underlying set evaluates candidate uniqueness against existing elements using object hashing and equality checks. If a matching element already exists, the set retains the existing element and ignores the new entry without throwing an exception.
-
Unordered Storage: In a HashSet, elements are bucketed based on internal hash calculations rather than insertion sequence. Traversing or printing the set yields elements in an arbitrary order that can shift across executions.
-
Immutable Set Enforcement: Set.of(...) instantiates internal, unmodifiable set classes. In these implementations, mutator methods (add, remove, clear) are overridden to unconditionally throw UnsupportedOperationException without inspecting their arguments.
Structural Comparisons
Common Set Implementations & Factory Variants
| Feature / Variant | HashSet | LinkedHashSet | TreeSet | Set.of(...) |
|---|---|---|---|---|
| Ordering | Unordered (arbitrary) | Insertion order | Sorted / Natural order | Unordered (arbitrary) |
| Allows Duplicates? | No | No | No | No (throws error if literals collide) |
| Mutability | Mutable (add/remove allowed) | Mutable (add/remove allowed) | Mutable (add/remove allowed) | Immutable (read-only) |
| Instantiation Type | Concrete class (new) | Concrete class (new) | Concrete class (new) | Static factory method on interface |
| Primary Use Case | General high-performance set | When insertion order must be kept | When sorted order is required | Fixed constant reference collections |
Common Anti-Patterns & Edge Cases
- Attempting to Instantiate
SetDirectly: WritingSet<String> items = new Set<>();triggers a compilation error:Set is abstract; cannot be instantiated. An implementing class likeHashSetmust be used instead. - Mutating
Set.ofCollections: Calling.add()or.remove()on a set created viaSet.of(...)compiles without warnings because the variable adheres to theSetinterface, but crashes at runtime with anUnsupportedOperationException. - Relying on Set Element Ordering: Assuming that elements added in a specific order to a
HashSetwill be printed or retrieved in that same order leads to logic bugs; standard sets are inherently unordered. - Expecting Duplicate Errors on
.add(): Expecting an exception when adding duplicates to a mutable set. Calling.add()with an existing element simply returnsfalseand leaves the set unchanged.
Knowledge Check & JetBrains-Style Coding Challenges
Theory Tips
- Interface Instantiation:
Setis an interface and cannot be instantiated directly withnew Set(). - Behavior of Duplicate Additions: Adding a duplicate element to a mutable
Setdoes not throw an error; the operation simply fails to store the element and returnsfalse. - Static Factory Immutability:
Set.of(...)produces an unmodifiable set; modifying it viaadd()orremove()causes a runtimeUnsupportedOperationException.
Question 1: Conceptual / Code Analysis
Consider the following code snippet:
import java.util.Set;
public class CollectionQuiz {
public static void main(String[] args) {
Set<String> colors = Set.of("red", "green", "blue");
colors.add("yellow");
System.out.println(colors.size());
}
}
What is the result when compiling and executing this program?
- A It prints
4to the console. - B A compilation error occurs because
Set.ofcannot accept string literals. - C A compilation error occurs because
addcannot be called on a variable of typeSet. - D The code compiles successfully, but throws an
UnsupportedOperationExceptionat runtime when callingadd.
Correct Answer
Correct Answer: D
Technical Breakdown:
- D is correct:
Set.of(...)returns an immutable (unmodifiable) instance ofSet. Becauseaddis declared in theSetinterface, the code passes compile-time type verification. However, at runtime, the immutable set implementation rejects modifications by throwing anUnsupportedOperationException. - A is incorrect: The element is never added because the collection cannot be modified.
- B is incorrect:
Set.of(...)accepts varargs/comma-delimited arguments of any object type, including string literals. - C is incorrect:
add(E e)is a standard method declared in theSetinterface, so the compiler accepts the invocation.
Question 2: Mini Coding Problem / Implementation Challenge
Task: Implement a tag registry manager named TagManager inside package collections_demo:
- Use an internal
HashSet<String>to store tags. - Implement method
public boolean addTag(String tag): adds the tag to the set and returnstrueif it was added (did not already exist), orfalseif it was a duplicate. - Implement method
public boolean hasTag(String tag): checks whether the tag exists in the registry. - Implement method
public int removeTag(String tag): removes the tag if present and returns the updatedsize()of the tag set. - Implement method
public Set<String> getImmutableSnapshot(): returns an immutable set containing the elements"alpha","beta", and"gamma"created viaSet.of(...).
Test Assertions:
TagManager tm = new TagManager();tm.addTag("java")Returns:truetm.addTag("java")Returns:falsetm.hasTag("java")Returns:truetm.removeTag("java")Returns:0tm.getImmutableSnapshot().size()Returns:3
Solution
package collections_demo;
import java.util.HashSet;
import java.util.Set;
public class TagManager {
// Internal mutable set holding unique tag strings
private final Set<String> tags = new HashSet<>();
public boolean addTag(String tag) {
// Set.add returns false if the item was already present
return tags.add(tag);
}
public boolean hasTag(String tag) {
return tags.contains(tag);
}
public int removeTag(String tag) {
tags.remove(tag);
return tags.size();
}
public Set<String> getImmutableSnapshot() {
// Creates an unmodifiable set via the static of() method
return Set.of("alpha", "beta", "gamma");
}
}
Complexity Analysis:
- Time Complexity: on average for
addTag,hasTag, andremoveTagoperations, as hash-based set lookups, insertions, and deletions execute in constant time. - Space Complexity: where is the number of unique tags maintained inside the underlying
HashSet.