Java provides three commonly used implementations of the Set
interface: HashSet, LinkedHashSet, and TreeSet. While they all guarantee uniqueness (no duplicate elements), they differ in their internal data structures and ordering behaviors. Understanding these differences is key to choosing the right implementation for your use case.
Internal Structure: Backed by a hash table.
Ordering: No guaranteed order of elements.
Performance:
add()
, remove()
, and contains()
are typically O(1), assuming a good hash function.Uniqueness: Relies on the hashCode()
and equals()
methods of the elements to determine uniqueness.
Example:
Set<String> hashSet = new HashSet<>();
hashSet.add("Banana");
hashSet.add("Apple");
hashSet.add("Cherry");
System.out.println(hashSet); // Output may vary: [Banana, Apple, Cherry]
HashSet is ideal when you don’t care about order and need fast access or lookup.
Internal Structure: Combines a hash table with a doubly-linked list.
Ordering: Preserves the order in which elements were inserted.
Performance:
HashSet
: add()
, remove()
, and contains()
are O(1).Uniqueness: Also relies on hashCode()
and equals()
.
Example:
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Banana");
linkedHashSet.add("Apple");
linkedHashSet.add("Cherry");
System.out.println(linkedHashSet); // Output: [Banana, Apple, Cherry]
Use
LinkedHashSet
when you need predictable iteration order without sorting.
Internal Structure: Implemented as a Red-Black Tree (a self-balancing binary search tree).
Ordering: Elements are sorted based on their natural order (i.e., via Comparable
) or a custom Comparator
.
Performance:
add()
, remove()
, contains()
) are O(log n).Uniqueness: Determined by compareTo() (or compare()
in Comparator
), not equals()
.
Example:
Set<String> treeSet = new TreeSet<>();
treeSet.add("Banana");
treeSet.add("Apple");
treeSet.add("Cherry");
System.out.println(treeSet); // Output: [Apple, Banana, Cherry]
Use
TreeSet
when elements need to be automatically sorted and you can tolerate slower performance.
Feature | HashSet | LinkedHashSet | TreeSet |
---|---|---|---|
Internal Structure | Hash Table | Hash Table + Linked List | Red-Black Tree |
Order Guarantee | None | Insertion Order | Sorted Order |
Performance (Avg) | O(1) | O(1) | O(log n) |
Allows Nulls? | Yes (1 null) | Yes (1 null) | Yes (if null-safe comparator not used) |
Requires Comparable | No | No | Yes (or custom Comparator) |
Maintains Duplicates | No | No | No |
Given input elements in the order: "Banana"
, "Apple"
, "Cherry"
:
HashSet → [Apple, Banana, Cherry] ← Order is arbitrary
LinkedHashSet → [Banana, Apple, Cherry] ← Preserves insertion order
TreeSet → [Apple, Banana, Cherry] ← Sorted (alphabetically)
Choosing the right Set implementation can significantly improve performance and maintainability based on your specific data handling needs.
The primary feature of a Set
in Java is uniqueness—a Set
cannot contain duplicate elements. Unlike List
, which allows duplicates and maintains element positions, Set
ensures that only one instance of any object (as defined by equality) exists in the collection.
The uniqueness contract in Set
relies on two key methods:
equals(Object o)
— Defines logical equality between objects.hashCode()
— Used by hash-based Sets (HashSet
, LinkedHashSet
) for quick lookups.When an element is added, the Set:
equals()
to compare against existing elements.Example: Duplicate Handling with HashSet
Set<String> names = new HashSet<>();
names.add("Alice");
names.add("Bob");
names.add("Alice"); // Duplicate
System.out.println(names); // Output: [Alice, Bob]
Even though
"Alice"
was added twice,HashSet
only retains one copy becauseString
overridesequals()
andhashCode()
properly.
Each Set
implementation has different behavior regarding the order of iteration:
Set Type | Maintains Order? | Description |
---|---|---|
HashSet |
❌ No order guarantee | Elements are unordered and may appear randomly. |
LinkedHashSet |
✅ Insertion order | Preserves the order in which elements were added. |
TreeSet |
✅ Sorted order | Maintains elements in natural or custom order. |
HashSet:
Set<String> hashSet = new HashSet<>();
hashSet.add("Banana");
hashSet.add("Apple");
hashSet.add("Cherry");
System.out.println("HashSet: " + hashSet);
// Output: Order is unpredictable, e.g., [Apple, Cherry, Banana]
LinkedHashSet:
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Banana");
linkedHashSet.add("Apple");
linkedHashSet.add("Cherry");
System.out.println("LinkedHashSet: " + linkedHashSet);
// Output: [Banana, Apple, Cherry]
TreeSet:
Set<String> treeSet = new TreeSet<>();
treeSet.add("Banana");
treeSet.add("Apple");
treeSet.add("Cherry");
System.out.println("TreeSet: " + treeSet);
// Output: [Apple, Banana, Cherry] — sorted alphabetically
All Set implementations reject duplicates, but the way they detect them depends on equals()
and hashCode()
or compareTo()
(in the case of TreeSet
).
Ordering varies by implementation:
HashSet
when order doesn't matter.LinkedHashSet
for predictable iteration order.TreeSet
when sorted order is required.Understanding how Sets handle uniqueness and order helps avoid surprises in your application’s logic, especially when processing user input, maintaining catalogs, or implementing caches.
TreeSet
is a powerful Set implementation that maintains its elements in sorted order. Unlike HashSet
or LinkedHashSet
, which depend on hashCode()
and equals()
for uniqueness and ordering, TreeSet
uses comparisons to determine element position and uniqueness. This comparison is performed using either the Comparable
interface or a Comparator
.
Comparable
If no custom comparator is provided, TreeSet
relies on the natural ordering of elements — that is, the element class must implement the Comparable<T>
interface and define the compareTo()
method.
import java.util.*;
class Person implements Comparable<Person> {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
// Natural ordering by age
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class TreeSetComparableExample {
public static void main(String[] args) {
Set<Person> people = new TreeSet<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 30)); // Treated as duplicate if compareTo returns 0
System.out.println(people);
// Output: [Bob (25), Alice (30)]
}
}
Note: If compareTo()
returns 0
, the elements are considered equal by TreeSet
, and the duplicate is not added, even if other fields differ.
Comparator
If you need multiple sort orders or the class does not implement Comparable
, you can pass a Comparator
to the TreeSet
constructor.
import java.util.*;
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class TreeSetComparatorExample {
public static void main(String[] args) {
// Sort by name instead of age
Comparator<Person> nameComparator = Comparator.comparing(p -> p.name);
Set<Person> people = new TreeSet<>(nameComparator);
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Alice", 40)); // Duplicate based on name
System.out.println(people);
// Output: [Alice (30), Bob (25)]
}
}
Here, "Alice (40)"
is not added because "Alice (30)"
already exists and the comparator considers them equal based on the name.
Feature | Comparable | Comparator |
---|---|---|
Interface method | compareTo(T o) |
compare(T o1, T o2) |
Where defined | Inside the class | Outside the class |
Natural or custom order | Natural | Custom |
Flexibility | One sort order | Multiple sort orders possible |
Comparable
when a class has a natural default order (e.g., age, name).Comparator
for flexible sorting strategies, especially when you can’t modify the class or need to sort by different fields.TreeSet
uses compareTo()
or compare()
not only for ordering but also to enforce uniqueness. So elements with the same sort key are treated as duplicates.Mastering Comparable
and Comparator
unlocks the full potential of TreeSet
in your Java collection toolbox.
In this section, we’ll look at how to use custom classes as elements in different Set
implementations—HashSet
, LinkedHashSet
, and TreeSet
. You’ll learn how to properly implement equals()
, hashCode()
, and Comparable
, and how to use a custom Comparator
to define ordering behavior.
import java.util.*;
class Book {
String title;
String author;
Book(String title, String author) {
this.title = title;
this.author = author;
}
// Required for HashSet/LinkedHashSet to detect duplicates
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false;
Book b = (Book) o;
return title.equals(b.title) && author.equals(b.author);
}
@Override
public int hashCode() {
return Objects.hash(title, author);
}
@Override
public String toString() {
return "\"" + title + "\" by " + author;
}
}
public class CustomSetDemo {
public static void main(String[] args) {
Set<Book> hashSet = new HashSet<>();
Set<Book> linkedHashSet = new LinkedHashSet<>();
Book b1 = new Book("Java Basics", "Alice");
Book b2 = new Book("Advanced Java", "Bob");
Book b3 = new Book("Java Basics", "Alice"); // Duplicate
hashSet.add(b1);
hashSet.add(b2);
hashSet.add(b3); // Ignored due to equals() and hashCode()
linkedHashSet.add(b1);
linkedHashSet.add(b2);
linkedHashSet.add(b3); // Ignored as well
System.out.println("HashSet: " + hashSet);
System.out.println("LinkedHashSet: " + linkedHashSet);
}
}
Output:
HashSet: ["Java Basics" by Alice, "Advanced Java" by Bob]
LinkedHashSet: ["Java Basics" by Alice, "Advanced Java" by Bob]
Explanation: Even though
b1
andb3
are different instances,HashSet
andLinkedHashSet
treat them as equal based on the overriddenequals()
andhashCode()
methods.
import java.util.*;
class Student {
String name;
int score;
Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return name + " (" + score + ")";
}
}
public class TreeSetCustomComparator {
public static void main(String[] args) {
// Sort by score descending
Comparator<Student> byScoreDesc = (s1, s2) -> Integer.compare(s2.score, s1.score);
Set<Student> students = new TreeSet<>(byScoreDesc);
students.add(new Student("Alice", 90));
students.add(new Student("Bob", 85));
students.add(new Student("Charlie", 90)); // Duplicate in sort key
System.out.println("TreeSet with custom Comparator: " + students);
}
}
Output:
TreeSet with custom Comparator: [Alice (90), Bob (85)]
Explanation: The two students with score
90
are considered equal by the comparator, so only the first is retained in theTreeSet
.
HashSet
and LinkedHashSet
, always override equals()
and hashCode()
for correct duplicate detection.TreeSet
, either implement Comparable
or provide a Comparator
. The sort key determines both order and uniqueness.0
.These examples demonstrate how to use Java sets effectively with custom objects while maintaining correct behavior around uniqueness and ordering.