1. Introduction
Let’s start with a real-life example. Imagine you’re organising a party and making a guest list. You send out invitations, and then it turns out the same person ended up on the list twice (or even three times — they really love parties!). If you use a regular list (List), such duplicates can easily appear. But if you had a collection that simply wouldn’t let you add the same guest twice — life would be easier.
That’s where the Set collection comes in — a set.
Set is a collection that stores only unique elements. If you try to add an element that already exists, it simply won’t be added (and no one will be offended).
Set interface: key properties
In Java, Set is an interface that defines the behaviour of a collection without duplicates. It extends the Collection interface, which means it supports operations such as add (add), remove (remove), contains (contains), and iteration.
Key features:
- Set cannot contain two equal elements.
- Elements can be stored in an arbitrary order — it depends on the concrete implementation.
- No indices: you can’t access an element by position like in a list.
Declaration syntax
Set<String> guests = new HashSet<>();
2. HashSet: fast, simple, unordered
HashSet is the most popular implementation of the Set interface. It is based on a hash table (like HashMap, but without the “key–value” pair, just unique values). Its main advantage is the speed of add, remove, and lookup operations.
How does HashSet work?
Imagine a box where you put things. To quickly figure out whether something similar is already inside, each item gets its own “number” — a hash code. When you add an element to a HashSet, this hash code is computed first. If it hasn’t been seen before, the element is quietly placed into the collection. If the hash already exists, equality is additionally checked via equals(). Only if the objects are actually equal will the new element not be added.
So, HashSet automatically takes care of uniqueness: two equal objects won’t appear in it.
An interesting point: if you’re working with your own classes and want to store them in a HashSet, you must override equals() and hashCode(). Without this, the collection may behave unpredictably — objects that seem identical might be considered different.
Main HashSet methods
Set<String> guests = new HashSet<>();
guests.add("Ivan");
guests.add("Maria");
guests.add("Pyotr");
guests.add("Ivan"); // Duplicate! Will not be added.
System.out.println(guests); // [Ivan, Maria, Pyotr] — order may be arbitrary
guests.remove("Pyotr"); // Remove an element
System.out.println(guests.contains("Maria")); // true
System.out.println(guests.size()); // 2
Let’s try this in code
Suppose in our application we want to store unique task names so that we don’t have two tasks with the same title:
import java.util.HashSet;
import java.util.Set;
public class UniqueTasksDemo {
public static void main(String[] args) {
Set<String> tasks = new HashSet<>();
tasks.add("Do Java homework");
tasks.add("Pet the cat");
tasks.add("Do Java homework"); // Duplicate!
System.out.println("Task list:");
for (String task : tasks) {
System.out.println("- " + task);
}
// Only two tasks will be in the list; the duplicate will not be added
}
}
3. TreeSet: order matters!
Sometimes we need not just uniqueness but also a sorted set of elements. For example, you might want to see guest names in alphabetical order rather than random order. That’s what TreeSet is for.
TreeSet is an implementation of the Set interface that stores elements in sorted order (ascending). It is based on a “red–black tree” structure.
TreeSet usage example
import java.util.Set;
import java.util.TreeSet;
public class SortedGuestsDemo {
public static void main(String[] args) {
Set<String> guests = new TreeSet<>();
guests.add("Vladimir");
guests.add("Alexey");
guests.add("Ekaterina");
guests.add("Alexey"); // Duplicate!
System.out.println("Guests (alphabetical):");
for (String guest : guests) {
System.out.println("- " + guest);
}
// Output:
// - Alexey
// - Vladimir
// - Ekaterina
}
}
Note: If you add a duplicate, it won’t appear in the set. Exactly as it should!
When to use TreeSet?
- When you need a sorted set of unique elements.
- When fast lookup matters but insertion speed is not critical (it’s a bit slower than HashSet).
- If the elements are your own classes, they must be “comparable” (implement Comparable) or you must provide a Comparator.
4. Useful details
HashSet vs TreeSet: which to choose?
| Criterion | HashSet | TreeSet |
|---|---|---|
| Storage order | Not guaranteed | Sorted ascending |
| Operation speed | Faster (O(1)) | Slower (O(log n)) |
| Type requirements | Any (equals()/hashCode() suffice) | Comparable or Comparator |
| Typical scenarios | When you need fast access to unique elements | When ordered output/traversal is important |
Set usage notes
- No indices. Unlike List, Set has no get(int index) method. If you need index-based access, use List.
- No duplicates. If you try to add an element that’s already present, it won’t be added. The add method will return false.
- Order not guaranteed (except TreeSet). In HashSet, element order may differ on each run. If you need insertion order, use LinkedHashSet.
- Null values.
- HashSet allows storing a single null element.
- TreeSet does not allow adding null without a special Comparator, otherwise you’ll get a NullPointerException.
5. Typical tasks for Set
Removing duplicates from a list
Suppose we have a list of students where some are listed twice. We need to keep only unique names:
import java.util.*;
public class RemoveDuplicatesDemo {
public static void main(String[] args) {
List<String> students = Arrays.asList("Anna", "Igor", "Anna", "Maria", "Igor", "Pavel");
Set<String> uniqueStudents = new HashSet<>(students);
System.out.println("Unique students: " + uniqueStudents);
// Order is not guaranteed!
}
}
If you need a sorted result, use TreeSet:
Set<String> sortedUniqueStudents = new TreeSet<>(students);
System.out.println("Unique students (alphabetical): " + sortedUniqueStudents);
Uniqueness check (e.g., a user’s login)
Set<String> usedLogins = new HashSet<>();
usedLogins.add("student1");
usedLogins.add("java_lover");
String newLogin = "student1";
if (usedLogins.contains(newLogin)) {
System.out.println("This login is already taken!");
} else {
System.out.println("The login is available!");
}
Iterating over a set
Iteration is done using a for-each loop:
for (String name : uniqueStudents) {
System.out.println(name);
}
6. Common mistakes when working with Set
Mistake No. 1: Expecting a specific element order in HashSet. Many beginners are surprised that set elements are printed in a “weird” order. That’s normal — HashSet doesn’t guarantee order. If you need insertion order, use LinkedHashSet; if you need sorting, use TreeSet.
Mistake No. 2: Trying to access an element by index. Sometimes people try to write something like set.get(0). You can’t do that: Set doesn’t support indexing. If you need index-based access, use List.
Mistake No. 3: Storing mutable objects. If you store objects whose fields participating in equals()/hashCode() can change, after such fields change the element can become “lost” for the set. Make elements immutable or don’t change identifying fields.
Mistake No. 4: Expecting duplicates to be added. Adding the same element multiple times will not increase the set size — duplicates are ignored, and the add method will return false.
Mistake No. 5: Using primitive types. A declaration like Set<int> won’t compile. Use wrapper classes: Set<Integer>, Set<Double>, etc.
GO TO FULL VERSION