SortedSet
SortedSet is an interface that extends Set and adds the contract that all elements are maintained in ascending order, determined either by their natural ordering (Comparable) or by a Comparator provided at construction. SortedSet defines six additional methods beyond Set: comparator(), first(), last(), headSet(), tailSet(), and subSet(). These methods enable range-based retrieval — extracting portions of the set based on element values rather than position. Understanding SortedSet as an interface separate from TreeSet is important for writing code that is generic over any sorted set implementation, and for understanding the semantics that all sorted set implementations must honour. This entry covers the SortedSet contract, its six methods, the ordering requirement, and how SortedSet fits into the NavigableSet extension.
The SortedSet Contract
// ── SortedSet interface hierarchy ─────────────────────────────────────
//
// Collection
// └── Set
// └── SortedSet ← this interface
// └── NavigableSet ← extends with navigation methods
// └── TreeSet ← primary implementation
// └── ConcurrentSkipListSet ← concurrent implementation
// ── SortedSet contract — ordering guarantees ──────────────────────────
SortedSet<String> sorted = new TreeSet<>();
sorted.add("cherry");
sorted.add("apple");
sorted.add("banana");
sorted.add("date");
// Iteration always in ascending natural order:
for (String s : sorted) System.out.print(s + " ");
// apple banana cherry date — ALWAYS
// toString() shows sorted order:
System.out.println(sorted); // [apple, banana, cherry, date]
// ── comparator() — retrieve the set's ordering policy ────────────────
SortedSet<String> naturalOrder = new TreeSet<>();
System.out.println(naturalOrder.comparator()); // null — uses Comparable
Comparator<String> byLength = Comparator.comparingInt(String::length);
SortedSet<String> customOrder = new TreeSet<>(byLength);
System.out.println(customOrder.comparator()); // the Comparator instance
// ── Comparator-based equality — NOT equals() ──────────────────────────
// PROBLEM: comparator only considers length — inconsistent with equals()
SortedSet<String> lengthSet = new TreeSet<>(byLength);
lengthSet.add("cat"); // length 3
lengthSet.add("dog"); // length 3 → comparator.compare("dog","cat") == 0
// → treated as DUPLICATE → NOT added!
lengthSet.add("horse"); // length 5 — different from 3 → added
System.out.println(lengthSet); // [cat, horse] — "dog" silently dropped!
System.out.println(lengthSet.contains("dog")); // false
System.out.println(lengthSet.contains("cat")); // true
System.out.println(lengthSet.contains("box")); // TRUE! "box" has length 3
// "box" is not in the set but comparator says it equals "cat"
// This violates Set semantics — contains("box") should return false
// ── Consistent comparator — correct behaviour ─────────────────────────
SortedSet<String> consistent = new TreeSet<>(
Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder()));
// Length-first, then lexicographic tie-break — consistent with equals()
consistent.add("cat");
consistent.add("dog"); // different string, different comparison result → added
consistent.add("horse");
System.out.println(consistent); // [cat, dog, horse]SortedSet Methods — first, last, headSet, tailSet, subSet
// ── SortedSet methods ────────────────────────────────────────────────
SortedSet<Integer> set = new TreeSet<>(Set.of(1, 3, 5, 7, 9, 11));
// first() and last()
System.out.println(set.first()); // 1 — minimum
System.out.println(set.last()); // 11 — maximum
// headSet(toElement) — elements STRICTLY LESS THAN toElement
SortedSet<Integer> head = set.headSet(7);
System.out.println(head); // [1, 3, 5] — all elements < 7
// tailSet(fromElement) — elements GREATER THAN OR EQUAL TO fromElement
SortedSet<Integer> tail = set.tailSet(7);
System.out.println(tail); // [7, 9, 11] — all elements >= 7
// subSet(from, to) — [from, to) — from inclusive, to exclusive
SortedSet<Integer> sub = set.subSet(3, 9);
System.out.println(sub); // [3, 5, 7] — from 3 (incl) to 9 (excl)
// ── Half-open interval convention ────────────────────────────────────
// [from, to) is consistent with:
// - Arrays: for (int i = from; i < to; i++)
// - Stream.range(from, to)
// - String.substring(from, to)
//
// headSet(5) means "everything before 5" = [..., 4]
// tailSet(5) means "5 and everything after" = [5, 6, ...]
// Together, headSet(5) and tailSet(5) partition the set at 5
// ── Partitioning a set ────────────────────────────────────────────────
SortedSet<Integer> numbers = new TreeSet<>(
Set.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
int pivot = 5;
SortedSet<Integer> below = numbers.headSet(pivot); // [1, 2, 3, 4]
SortedSet<Integer> atOrAbove = numbers.tailSet(pivot); // [5, 6, 7, 8, 9, 10]
// Verify: union covers all elements, no overlap
assert below.size() + atOrAbove.size() == numbers.size();
assert Collections.disjoint(below, atOrAbove);
// ── Live view mutations ───────────────────────────────────────────────
SortedSet<String> names = new TreeSet<>(
Set.of("Alice", "Bob", "Carol", "Dave", "Eve"));
SortedSet<String> aToD = names.subSet("A", "E");
System.out.println(aToD); // [Alice, Bob, Carol, Dave]
aToD.remove("Bob"); // removes "Bob" from original set too
System.out.println(names); // [Alice, Carol, Dave, Eve] — Bob removed
names.add("Ann"); // "Ann" < "E" — within range — appears in view
System.out.println(aToD); // [Alice, Ann, Carol, Dave]
names.add("Frank"); // "Frank" > "E" — outside view range
System.out.println(aToD); // [Alice, Ann, Carol, Dave] — Frank not visibleProgramming to SortedSet — Generic Sorted Set Code
// ── Generic sorted set operations ────────────────────────────────────
// Accept SortedSet<T> — works with TreeSet, ConcurrentSkipListSet, etc.
public static <T> List<T> getPage(SortedSet<T> set,
T fromExclusive,
int pageSize) {
SortedSet<T> from = (fromExclusive == null)
? set
: set.tailSet(fromExclusive); // elements after the cursor
// Skip the cursor element itself if it is in the set
// (tailSet is inclusive; we want exclusive)
List<T> page = new ArrayList<>();
Iterator<T> it = from.iterator();
// Skip fromExclusive if present
if (fromExclusive != null && it.hasNext() &&
set.contains(fromExclusive)) {
it.next(); // skip the cursor
}
while (it.hasNext() && page.size() < pageSize) {
page.add(it.next());
}
return page;
}
// Usage:
SortedSet<String> names = new TreeSet<>(
Set.of("Alice", "Bob", "Carol", "Dave", "Eve", "Frank"));
List<String> page1 = getPage(names, null, 3); // [Alice, Bob, Carol]
List<String> page2 = getPage(names, "Carol", 3); // [Dave, Eve, Frank]
// ── Range count — how many elements in [from, to) ────────────────────
public static <T> int countInRange(SortedSet<T> set, T from, T to) {
return set.subSet(from, to).size(); // O(log n) to create view + O(k) to count
}
SortedSet<Integer> scores = new TreeSet<>(
Set.of(45, 62, 71, 78, 85, 91, 96));
System.out.println(countInRange(scores, 70, 90)); // 2 (71, 78)
// ── Minimum element satisfying a condition ────────────────────────────
public static <T> Optional<T> firstSatisfying(SortedSet<T> set,
Predicate<T> predicate) {
for (T element : set) { // iterates in sorted order
if (predicate.test(element)) {
return Optional.of(element); // first (smallest) satisfying element
}
}
return Optional.empty();
}
// First score >= 70 (passing grade):
Optional<Integer> firstPassing = firstSatisfying(scores, s -> s >= 70);
System.out.println(firstPassing.orElse(-1)); // 71
// Note: NavigableSet.ceiling(70) is O(log n) and more efficient
// but firstSatisfying is generic for arbitrary predicates
// ── Programmatic sorted set creation ─────────────────────────────────
// Factory method that returns appropriate implementation by context:
public static <T extends Comparable<T>> SortedSet<T> createSortedSet(
boolean concurrent) {
return concurrent
? new ConcurrentSkipListSet<>()
: new TreeSet<>();
}