NavigableSet
NavigableSet extends SortedSet with a rich set of navigation methods that go beyond the simple range-view operations of SortedSet. It adds the ability to find the closest element to a given value (floor, ceiling, lower, higher), to poll (retrieve and remove) the minimum and maximum elements, to obtain a reversed view of the set, to obtain an iterator in descending order, and to control the inclusivity of range bounds precisely. NavigableSet is the interface that TreeSet and ConcurrentSkipListSet implement, and understanding it completely unlocks the full power of sorted collections in Java. This entry covers every NavigableSet method in depth, the descending set and iterator, the extended subSet with inclusivity control, and the practical patterns that navigation methods enable.
NavigableSet Methods — The Complete API
// ── All four navigation methods with semantics ───────────────────────
NavigableSet<Integer> set = new TreeSet<>(
Set.of(10, 20, 30, 40, 50));
// floor(e): greatest element <= e
System.out.println(set.floor(25)); // 20 — 25 not in set, greatest <= 25 is 20
System.out.println(set.floor(30)); // 30 — exact match
System.out.println(set.floor(5)); // null — nothing <= 5
// ceiling(e): least element >= e
System.out.println(set.ceiling(25)); // 30 — 25 not in set, least >= 25 is 30
System.out.println(set.ceiling(30)); // 30 — exact match
System.out.println(set.ceiling(55)); // null — nothing >= 55
// lower(e): greatest element STRICTLY < e (never equal)
System.out.println(set.lower(25)); // 20 — strictly less than 25
System.out.println(set.lower(30)); // 20 — strictly less than 30 (not 30 itself!)
System.out.println(set.lower(10)); // null — nothing strictly less than 10
// higher(e): least element STRICTLY > e (never equal)
System.out.println(set.higher(25)); // 30 — strictly greater than 25
System.out.println(set.higher(30)); // 40 — strictly greater than 30 (not 30!)
System.out.println(set.higher(50)); // null — nothing strictly greater than 50
// ── pollFirst and pollLast ────────────────────────────────────────────
NavigableSet<String> names = new TreeSet<>(
Set.of("Alice", "Bob", "Carol", "Dave"));
String first = names.pollFirst(); // "Alice" — removed from set
String last = names.pollLast(); // "Dave" — removed from set
System.out.println(names); // [Bob, Carol]
// Safe on empty set — returns null, does not throw:
NavigableSet<Integer> empty = new TreeSet<>();
System.out.println(empty.pollFirst()); // null
System.out.println(empty.pollLast()); // null
// ── Navigation chaining — walk the sequence ───────────────────────────
NavigableSet<Integer> primes = new TreeSet<>(
Set.of(2, 3, 5, 7, 11, 13, 17, 19, 23));
// Walk in steps: next prime after 10, then after that, etc.
Integer current = primes.higher(10); // 11
while (current != null) {
System.out.print(current + " ");
current = primes.higher(current); // next prime
}
// 11 13 17 19 23Descending Views and Iterators
// ── descendingIterator — efficient reverse traversal ─────────────────
NavigableSet<String> set = new TreeSet<>(
Set.of("Alice", "Bob", "Carol", "Dave", "Eve"));
// Ascending (default):
for (String s : set) System.out.print(s + " ");
// Alice Bob Carol Dave Eve
// Descending via iterator:
Iterator<String> desc = set.descendingIterator();
while (desc.hasNext()) System.out.print(desc.next() + " ");
// Eve Dave Carol Bob Alice
// Enhanced for on descendingSet():
for (String s : set.descendingSet()) System.out.print(s + " ");
// Eve Dave Carol Bob Alice — same result
// ── descendingSet — live view with reversed NavigableSet semantics ────
NavigableSet<Integer> original = new TreeSet<>(Set.of(1, 3, 5, 7, 9));
NavigableSet<Integer> reversed = original.descendingSet();
// The reversed view's first/last are swapped:
System.out.println(original.first()); // 1 — minimum
System.out.println(reversed.first()); // 9 — maximum in original = first in reversed
System.out.println(original.last()); // 9
System.out.println(reversed.last()); // 1
// Navigation methods operate in the reversed direction:
System.out.println(reversed.floor(6)); // 7 — in reversed view, floor means
// greatest of the elements <= 6 in reversed order
// which is ceiling(6) in original = 7
System.out.println(reversed.ceiling(6)); // 5 — ceiling in reversed = floor in original
// ── Mutations through descending view ─────────────────────────────────
NavigableSet<Integer> descView = original.descendingSet();
descView.add(6); // adds 6 to original
System.out.println(original); // [1, 3, 5, 6, 7, 9] — 6 added
descView.remove(9); // removes 9 from original
System.out.println(original); // [1, 3, 5, 6, 7] — 9 removed
// ── Double reversal returns to original ordering ──────────────────────
NavigableSet<Integer> doubleReversed = original.descendingSet().descendingSet();
// doubleReversed is functionally equivalent to original
System.out.println(doubleReversed.first()); // 1 — same as original.first()Extended subSet — Full Inclusivity Control
// ── All four interval types ───────────────────────────────────────────
NavigableSet<Integer> set = new TreeSet<>(
Set.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// Closed interval [3, 7]
NavigableSet<Integer> closed = set.subSet(3, true, 7, true);
System.out.println(closed); // [3, 4, 5, 6, 7]
// Open interval (3, 7)
NavigableSet<Integer> open = set.subSet(3, false, 7, false);
System.out.println(open); // [4, 5, 6]
// Half-open [3, 7) — same as SortedSet.subSet(3, 7)
NavigableSet<Integer> halfOpenLeft = set.subSet(3, true, 7, false);
System.out.println(halfOpenLeft); // [3, 4, 5, 6]
// Half-open (3, 7]
NavigableSet<Integer> halfOpenRight = set.subSet(3, false, 7, true);
System.out.println(halfOpenRight); // [4, 5, 6, 7]
// ── headSet and tailSet with inclusivity ──────────────────────────────
// All elements <= 5 (inclusive)
NavigableSet<Integer> head = set.headSet(5, true);
System.out.println(head); // [1, 2, 3, 4, 5]
// All elements > 5 (exclusive — same as default tailSet behaviour for > 5)
NavigableSet<Integer> tailExcl = set.tailSet(5, false);
System.out.println(tailExcl); // [6, 7, 8, 9, 10]
// ── Range operations on the view ─────────────────────────────────────
NavigableSet<Integer> range = set.subSet(3, true, 8, true);
// Count elements in range:
System.out.println(range.size()); // 6 (3,4,5,6,7,8)
// Remove all elements in range:
range.clear();
System.out.println(set); // [1, 2, 9, 10] — range removed from original
// ── Practical: appointment scheduler ─────────────────────────────────
NavigableSet<LocalDateTime> appointments = new TreeSet<>();
appointments.add(LocalDateTime.of(2024, 3, 15, 9, 0));
appointments.add(LocalDateTime.of(2024, 3, 15, 10, 30));
appointments.add(LocalDateTime.of(2024, 3, 15, 14, 0));
appointments.add(LocalDateTime.of(2024, 3, 15, 16, 30));
LocalDateTime start = LocalDateTime.of(2024, 3, 15, 10, 0);
LocalDateTime end = LocalDateTime.of(2024, 3, 15, 15, 0);
// Appointments strictly during the window (exclusive start and end):
NavigableSet<LocalDateTime> inWindow =
appointments.subSet(start, false, end, false);
System.out.println(inWindow.size()); // 2 (10:30 and 14:00)
// Next appointment from now:
LocalDateTime now = LocalDateTime.now();
LocalDateTime next = appointments.ceiling(now);
// If next is null, no upcoming appointmentsNavigableSet Patterns — Algorithms and Real-World Use
// ── Order statistics — rank queries ──────────────────────────────────
NavigableSet<Integer> scores = new TreeSet<>(
Set.of(45, 62, 71, 78, 85, 91, 96, 99));
int target = 80;
// How many scores are less than 80?
int rank = scores.headSet(target, false).size(); // 4 (45,62,71,78)
System.out.println("Scores below " + target + ": " + rank);
// What percentile is 80?
double percentile = (double) rank / scores.size() * 100;
System.out.printf("Percentile: %.0f%%
", percentile); // 50%
// ── K-nearest elements to target ──────────────────────────────────────
public List<Integer> kNearest(NavigableSet<Integer> set,
int target, int k) {
// Use two pointers: one scanning down (lower), one scanning up (higher)
List<Integer> result = new ArrayList<>();
Integer lower = set.floor(target);
Integer upper = set.ceiling(target);
// If target is in set, add it and advance upper
if (lower != null && lower.equals(upper)) {
result.add(lower);
lower = set.lower(lower);
if (result.size() == k) return result;
}
while (result.size() < k && (lower != null || upper != null)) {
if (lower == null) {
result.add(upper);
upper = set.higher(upper);
} else if (upper == null) {
result.add(lower);
lower = set.lower(lower);
} else if (Math.abs(target - lower) <= Math.abs(target - upper)) {
result.add(lower);
lower = set.lower(lower);
} else {
result.add(upper);
upper = set.higher(upper);
}
}
return result;
}
NavigableSet<Integer> data = new TreeSet<>(
Set.of(1, 5, 10, 15, 20, 25, 30));
System.out.println(kNearest(data, 12, 3)); // [10, 15, 5] — 3 nearest to 12
// ── Finding gaps in a sequence ────────────────────────────────────────
// Find the first missing integer in a sorted set of reserved IDs
public int firstAvailableId(NavigableSet<Integer> reserved, int start) {
Integer next = reserved.ceiling(start);
while (next != null && next.equals(start)) {
start++;
next = reserved.ceiling(start);
}
return start;
}
NavigableSet<Integer> reserved = new TreeSet<>(Set.of(1, 2, 3, 5, 6, 9));
System.out.println(firstAvailableId(reserved, 1)); // 4 — first gap after 1
// ── Using NavigableSet vs SortedSet in method signatures ──────────────
// Use SortedSet when only first(), last(), subSet(), headSet(), tailSet() needed
// Use NavigableSet when floor(), ceiling(), lower(), higher(),
// descendingSet(), pollFirst(), or inclusivity control needed
public static <T> Optional<T> findCeiling(NavigableSet<T> set, T target) {
return Optional.ofNullable(set.ceiling(target));
}
public static <T> int countBetween(SortedSet<T> set, T from, T to) {
return set.subSet(from, to).size(); // SortedSet sufficient for this
}