Java Set, HashSet, TreeSet, and LinkedHashSet
A set keeps distinct members, but uniqueness alone does not answer how they should appear. Learn membership, first-seen order, sorted order, and equality, then clean a real-looking list of user-entered topic tags.
Before you start: review List and copying, generics and equality, and Strings. Examples are complete Java 21 Main.java programs with no input required. Run code uses the existing isolated Java sandbox when Docker is available; no fallback runs code on the website host.
What you will be able to do
- Explain how set membership differs from a list of repeated entries.
- Use add, contains, and remove and interpret their results.
- Choose unspecified, insertion, or sorted encounter order deliberately.
- Distinguish hash collisions from equality and comparison-based uniqueness.
- Combine sets on independent copies without editing borrowed inputs.
- Normalize and deduplicate tags while retaining first-seen order.
1. Ask whether a value is present, not how many times it occurred
A student can visit the same lesson five times, but a topic filter should usually show that lesson’s topic once. A List can preserve all five visits. A Set answers a different question: which distinct members are present? If repetition carries meaning, keep the list or count occurrences separately rather than discarding information accidentally.
In the example, the first add changes membership and returns true. Adding the same String again returns false because the set already contains an equal value. size counts distinct members, contains checks membership, and remove reports whether it removed a member. These return values let you respond to a change without doing a separate search first.
Set does not provide get(index). Even when an implementation has a predictable encounter order, it is not a List with indexed slots. Traverse it with an enhanced for loop when you need each member. Notice that our HashSet example prints booleans and a count, not its iteration sequence: membership and display order are separate requirements.
A repeated add does not create another member
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
First add: true
Again: false
Count: 2
Has SQL: true
Removed: true
Removed again: false
2. LinkedHashSet remembers ordinary insertion order
Suppose users enter tags as sql, java, css, sql. You want one chip per distinct tag but do not want the chips to jump into a different sequence. LinkedHashSet combines uniqueness with insertion-order traversal. The first successful insertion establishes a member’s place; an ordinary add of an existing member leaves that place unchanged.
The example first prints [sql, java, css]. Repeating sql did not move it to the end. Removing sql and adding it again is different: it becomes a new last insertion and the result is [java, css, sql]. Java 21 also offers explicit sequencing operations on LinkedHashSet, but this lesson uses ordinary add and remove so the first-seen policy stays clear.
When you construct a LinkedHashSet from another collection, it follows the source’s encounter sequence. It cannot recover historical insertion order that a HashSet never promised. For our tag-cleaning lab, the source is a List whose sequence is intentional, and normalization happens before each insertion. That makes the meaning of ‘first seen’ precise.
Keep first-seen order with LinkedHashSet
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
First-seen: [sql, java, css]
After remove/add: [java, css, sql]
3. TreeSet keeps values in an ordering, not arrival order
A glossary often needs a sorted list of unique topic labels, regardless of which label arrived first. TreeSet maintains an order as members are added and removed. With ordinary Strings and no supplied Comparator, it uses String natural order. first and last expose the endpoints through SortedSet, which extends the basic Set contract.
The all-lowercase ASCII labels in the example appear as css, java, sql. Natural String order compares UTF-16 code units; it is not a promise of human dictionary order for every language. Mixed case and accented text can produce results that surprise readers. Locale-aware collation is a separate policy, not something TreeSet invents automatically.
A TreeSet decides whether two candidates occupy the same place using comparison: a comparison result of zero means one member for that ordering. To honor the general Set equality contract, that order should be consistent with equals. An overly broad comparator can therefore discard distinctions you intended to keep. The repair exercise demonstrates this with case-sensitive identifiers.
A TreeSet presents natural order
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Sorted: [css, java, sql]
First: css
Last: sql
4. Choose the order you need; do not infer it from a printout
Run the same input through the three implementations and ask two separate questions: do they contain the expected members, and how do they present them? For these Strings with natural equality and ordering, the membership is the same. LinkedHashSet follows first encounter, TreeSet follows natural order, and HashSet promises neither sequence.
A HashSet may happen to print the same sequence across several runs. That observation is not an API guarantee, and its order may change with different values or internal changes. The example deliberately avoids an exact expected HashSet sequence. For a deterministic display, choose a defined-order implementation or explicitly sort a copy for presentation.
Set equality normally checks membership rather than encounter order. Two LinkedHashSets with reversed insertion sequences can still be equal. If presentation sequence matters to a test, compare an explicit List created from the set’s traversal as well as its contents. This distinction prevents a test from passing even though user-visible chips appear in the wrong order.
Same members, different presentation policies
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Same members: true
First-seen: [sql, java, css]
Sorted: [css, java, sql]
Hash membership count: 3
5. A hash code narrows a search; equality decides membership
HashSet uses hashing to organize candidates and equals to distinguish values. Equal objects must have equal hash codes, but equal hash codes do not imply equal objects. The two short Strings FB and Ea demonstrate a collision: their hash codes match, while their contents do not. Both must remain members of the set.
Think of a hash code as a way to choose a search area, not as a unique ID. It is unsafe to replace your own domain equality with ‘the hash numbers match’. The example checks the collision, the unequal contents, and the final membership count separately. No claim about the order of those two members is needed.
HashSet membership operations are typically constant-time when hashes are well distributed; that is not a guarantee for every workload. LinkedHashSet adds ordering bookkeeping, while TreeSet basic operations have logarithmic costs. Choose semantics first, then measure representative workloads if performance matters. These tiny examples prove behavior, not a benchmark winner.
A hash collision does not make two values equal
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Same hash: true
Equal: false
Unique: 2
Both present: true
6. Your objects need a stable definition of equal
Two separately created Tag objects can represent the same topic. Without a value-equality implementation, an ordinary class inherits identity-based Object equality and those objects remain distinct. Our small Tag class defines equality using its non-null name and derives hashCode from that same name, so two java tags become one member.
Read the methods together. equals first checks the compatible object type, then compares names. hashCode uses the very field equality uses. The final class and final String field keep that equality state stable. Adding a new object with an equal name returns false, and searching with another equal object still succeeds; reference identity is not required.
Do not mutate fields involved in equality or hashing while an object is inside a hash-based set. Likewise, changing ordering fields inside a TreeSet can break lookup assumptions. Prefer stable value objects; otherwise remove the object before changing its key and reinsert afterward, handling any resulting duplicate deliberately. A set is not a live index that automatically tracks arbitrary field changes.
Define value equality for a domain object
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
true
false
Count: 1
Found by value: true
Trace two separate Tag objects
- The first Tag with name java is inserted.
- The second Tag is a different object but has an equal name and matching hash code.
- The set keeps one member, and contains finds it using another equal Tag.
As an experiment, remove both overrides from the runner and predict the count. Do not remove only hashCode while retaining value equals: that would break the equality/hash contract.
7. Combine memberships on copies when inputs are borrowed
Imagine comparing two students’ topic selections. A union contains topics either selected, an intersection contains topics both selected, and left-minus-right contains topics selected only by the first student. The example implements these operations with addAll, retainAll, and removeAll on fresh LinkedHashSet containers.
These bulk methods mutate the receiver; they do not return a new result collection. Calling left.retainAll(right) would erase left-only topics from the original left set. Creating a copy before the operation keeps the input intact. The union preserves left’s encounter sequence and then appends previously unseen right members in right’s encounter sequence.
The copy is shallow: element references are shared, not recursively cloned. Our Strings are immutable, so structural copies are sufficient for these demonstrations. More generally, the equality and comparison rules of the participating collections must be compatible. The example intentionally uses the same String equality on both sides rather than mixing inconsistent comparators.
Union, intersection, and difference on copies
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Either: [java, sql, css]
Both: [sql]
Only left: [java]
Original left: [java, sql]
8. Validate domain rules instead of inheriting accidental permissiveness
HashSet and LinkedHashSet permit a null member, but a natural-order TreeSet rejects null. A specially supplied comparator can define different null behavior, so ‘every TreeSet always rejects null’ is too broad. The example specifically constructs a natural-order tree and makes that boundary visible with an intentional, caught failure.
Set.of produces an unmodifiable set, rejects null, and rejects duplicate arguments rather than silently removing them. Set.copyOf also produces an unmodifiable set, but can accept a collection with repeated equal elements. Neither factory promises first-seen iteration order. Do not replace LinkedHashSet with one of them if insertion sequence is part of your result contract.
For user-entered tags, permissive containers do not define good application data. The lab rejects null and blank input, strips surrounding whitespace, lowercases with Locale.ROOT, and only then deduplicates. This is an explicit policy for simple topic labels, not a universal Unicode identity rule. It preserves source data and returns a separate editable first-seen set.
The implementation determines null support
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Hash null count: 1
Natural-order TreeSet rejects null.
Repair exercise: two case-sensitive identifiers became one
These identifiers are case-sensitive, so Java and java must both survive. The starter uses a case-insensitive TreeSet and silently loses one spelling. Repair its construction so uniqueness matches String.equals while the output remains naturally sorted.
Main.java · Find the comparator mismatch
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Current result:
Count: 2
[Java, SQL]
Reveal the explained repair
The case-insensitive comparator returns zero for Java and java, so the tree regards them as the same member even though String.equals says otherwise. A natural-order TreeSet distinguishes these spellings and sorts them consistently with String equality. In the lab, case-insensitive deduplication is instead an explicit normalization step before insertion, making the intended stored values visible.
Main.java · Preserve distinct spellings
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Count: 3
[Java, SQL, java]
Independent lab: clean tags without losing first-seen order
Prepare topic-filter chips from user-entered tags. Treat surrounding whitespace and letter case as formatting differences, preserve the first encounter of each normalized tag, and leave the original list unchanged. Display a sorted copy separately without changing the first-seen result.
- Implement cleanTags(List<String> raw), returning a new editable Set<String>.
- Reject a null list or a null/blank entry with IllegalArgumentException.
- Normalize with strip().toLowerCase(Locale.ROOT) before testing membership.
- Use LinkedHashSet to preserve first-seen order after normalization.
- Keep the source untouched, including when a later entry is invalid; allow an empty source.
- Print first-seen tags, a naturally sorted copy, and the number of discarded duplicate occurrences.
The starter compiles but prints nothing. Implement the helper and its report to reach this expected output. Whitespace remains visible in the original list because it must not be edited:
First-seen: [java, sql, css]
Sorted copy: [css, java, sql]
Duplicates removed: 2
Original: [ Java , SQL, java, CSS , sql]
Main.java · Normalize then deduplicate
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Stage 1 hints
Stage 1: validate and normalize. Reject a null list. Read each reference as a String and check for null before calling strip. Use Locale.ROOT explicitly instead of the machine’s default locale.
Stage 2 hints
Stage 2: add normalized values to a new LinkedHashSet. Do not deduplicate raw spellings first. Repeated add calls keep the earliest normalized member’s position without growing the set.
Stage 3 hints
Stage 3: keep presentations separate. Return the first-seen set and construct a TreeSet from it only for sorted output. Subtract unique count from raw count after successful validation to count discarded occurrences.
Reveal the complete explained solution
Main.java · Keep both presentation policies explicit
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Java and java both normalize to java, so the second occurrence does not change membership or order. The same happens to SQL and sql. The helper changes only its new container; an invalid later value cannot leave a partly normalized source behind. A TreeSet constructed from the result gives a separate sorted presentation. The helper is single-threaded and assumes callers do not modify its input concurrently.
Test your contract
- An empty list yields an empty, editable set.
- Adjacent and separated duplicate spellings collapse after normalization.
- First-seen iteration order must be checked as a List, not only with Set.equals.
- Null, empty, and strip-whitespace-only entries are rejected without editing input.
- Test a Turkish default locale: I must still normalize using Locale.ROOT, and editing the returned set must not edit the source.
Use new ArrayList<>(tags) to inspect encounter order in a test. A Set.equals assertion alone is insufficient. To test a null entry, add it explicitly to an ArrayList; List.of rejects it before your helper runs.
Common mistakes and how to diagnose them
Relying on HashSet display order
A repeated print sequence does not establish a guarantee. Assert membership or choose LinkedHashSet/TreeSet when the display order matters. Sorting a copy makes presentation explicit.
Deduplicating before normalizing
Java, java, and a spaced spelling are different raw Strings. Apply the domain’s normalization policy first, then insert. Do not silently assume case or whitespace rules the application never defined.
Treating a hash code as a unique ID
Different values can share a hash code. Correct equality still distinguishes colliding values. Implement equals and hashCode together, using consistent and stable state.
A comparator discards distinct values
TreeSet uses comparison zero for uniqueness. Check whether your ordering merges values that equals distinguishes; inconsistent ordering violates the general Set contract even if the tree’s own behavior is predictable.
Bulk operations erase an input
retainAll, removeAll, and addAll mutate the receiver. Copy borrowed data first when you need a separate result, and remember that copying does not deep-clone element objects.
A set equality test misses an ordering bug
Set.equals normally compares members, not encounter sequence. Check new ArrayList<>(result) against the expected sequence when first-seen order is part of the contract.
Frequently asked questions
Which implementation should I start with?
Choose from the requirement: HashSet for membership without a sequence promise, LinkedHashSet for predictable ordinary insertion order, and TreeSet for a defined sorted order. No implementation is universally best.
Can I retrieve the item at index zero?
Not through Set.get, because there is no such method. Traverse the set or use a more specific sequencing/sorted interface when appropriate. If indexed positions and repeated entries are central to the task, a List may be the right contract.
Does adding an existing LinkedHashSet member move it?
Ordinary add does not move an existing member. Removing it and adding it later inserts it at the end. Java 21 explicit sequencing methods can reposition members, which is a separate operation from ordinary add.
Why not use Set.of for duplicate input?
Set.of rejects duplicate arguments with IllegalArgumentException. It is an unmodifiable factory, not a duplicate-cleaning workflow. Set.copyOf can collapse repeated equal input values but does not promise the lab’s first-seen encounter order.
Does lowercase with Locale.ROOT solve all text identity problems?
No. It avoids dependence on the machine’s default locale, but is not full Unicode case folding or normalization. Visually similar or canonically equivalent text may still differ. This lesson defines only strip plus Locale.ROOT lowercasing for simple topic labels.
Does a copied set protect mutable elements?
No. Copying creates a new container with shared element references. Prefer stable value objects for equality/hash/ordering fields; mutating those fields while stored can break membership assumptions.
Uniqueness, equality, and order are separate decisions
A set stores distinct members, not occurrence counts. Hash-based sets need consistent equals and hashCode; TreeSet uses comparison. Choose a sequence guarantee deliberately, normalize according to an explicit domain policy, and copy borrowed data before bulk mutations.
Before moving on, explain why a hash collision can retain two members, why the comparator repair restores a missing spelling, and why the lab must normalize before insertion. Next: Map implementations, where each key leads to a value.
Can you choose uniqueness and ordering deliberately?
Answer six questions and read the explanations. Quiz answers are temporary and do not save course progress.
References and related learning
This original SovranCode lesson uses the official Java 21 APIs to verify set contracts, equality, and ordering.
Review lists · Review typed collections · Java course roadmap.
Last updated: