Extending Collection with Set, SortedSet and NavigableSet
Exploring the Set Interface
The Set interface does not bring any new method to the Collection interface. The Collections Framework gives you one plain implementation of the Set interface: HashSet. Internally, a HashSet wraps an instance of HashMap, a class that will be covered later, that acts as a delegate for HashSet.
As you already saw, what a Set brings to a Collection is that it forbids duplicates. What you lose over the List interface is that your elements are stored in no particular order. There is very little chance that you will iterate over them in the same order as you added them to your set.
You can see this in the following example:
List<String> strings = List.of("one", "two", "three", "four", "five", "six");
Set<String> set = new HashSet<>();
set.addAll(strings);
set.forEach(System.out::println);
Running this code will produce the following:
six
four
one
two
three
five
Some implementations of Set give you the same order when you iterate over their elements, but since this is not guaranteed, your code should not rely on that.
Extending Set with SortedSet
The first extension of Set is the SortedSet interface. The SortedSet interface keeps its elements sorted according to a certain comparison logic. The Collections Framework gives you one implementation of SortedSet, called TreeSet.
As you already saw, either you provide a comparator when you build a TreeSet, or you implement the Comparable interface for the elements you put in the TreeSet. If you do both, then the comparator takes precedence.
The SortedSet interface adds new methods to Set.
first()andlast()returns the lowest and the largest elements of the setheadSet(toElement)andtailSet(fromElement)returns you subsets containing the elements lower thantoElementor greater thanfromElementsubSet(fromElement, toElement)gives you a subset of the element betweenfromElementandtoElement.
The toElement and fromElement do not have to be elements of the main set. If they are, then toElement is not included in the result and fromElement is, following the usual convention.
Consider the following example:
SortedSet<String> strings = new TreeSet<>(Set.of("a", "b", "c", "d", "e", "f"));
SortedSet<String> subSet = strings.subSet("aa", "d");
System.out.println("sub set = " + subSet);
Running this code will give you the following:
sub set = [b, c]
The three subsets that these methods return are views on the main set. No copy is made, meaning that any change you make to these subsets will be reflected in the set, and the other way round.
You can remove or add elements to the main set through these subsets. There is one point you need to keep in mind though. These three subsets remember the limits on which they have been built. For consistency reasons, it is not legal to add an element through a subset outside its limits. For instance, if you take a headSet and try to add an element greater than toElement, then you will get an IllegalArgumentException.
Extending SortedSet with NavigableSet
Java SE 6 saw the introduction of an extension of SortedSet with the addition of more methods. It turns out that the TreeSet class was retrofitted to implement NavigableSet. So you can use the same class for both interfaces.
Some methods are overloaded by NavigableSet.
headSet(),headSet(), andheadSet()may take a furtherbooleanarguments to specify whether the limits (toElementorfromElement) are to be included in the resulting subset.
Other methods have been added.
ceiling(element), andfloor(element)return the greatest element lesser or equal than, or the lowest element greater or equal than the providedelement. If there is no such element thennullis returnedfloor(element), andhigher(element)return the greater element lesser than, or the lowest element greater than the providedelement. If there is no such element thennullis returned.pollFirst(), andpollLast()return and removes the lowest or the greatest element of the set.
Furthermore, NavigableSet also allows you to iterate over its elements in descending order. There are two ways to do this.
- You can call
descendingIterator(): it gives you a regularIteratorthat traverses the set in the descending order. - You can also call
descendingSet(). What you get in return is anotherNavigableSetthat is a view on this set and that makes you think you have the same set, sorted in the reversed order.
The following example demonstrates this.
NavigableSet<String> sortedStrings = new TreeSet<>(Set.of("a", "b", "c", "d", "e", "f"));
System.out.println("sorted strings = " + sortedStrings);
NavigableSet<String> reversedStrings = sortedStrings.descendingSet();
System.out.println("reversed strings = " + reversedStrings);
Running this code will give you the following:
sorted strings = [a, b, c, d, e, f]
reversed strings = [f, e, d, c, b, a]
Last update: September 14, 2021