Oh good, a chance to rant about one of my pet peeves. Sorted sets and maps are really useful for all sorts of things, but in Java, they are very much second-class citizens. This JEP touches on a few ways in which they lack lustre. But take a look at even creating them in the first place!<p>Firstly, assume we have a string lying around:<p><pre><code> String string = "mississippi";
</code></pre>
Let's create some unsorted collections, from a literal (ish) expression, collecting a stream to a set, and collecting a stream to a map:<p><pre><code> Set<Integer> literal = Set.of(1, 2, 3);
Set<Integer> collected = string.chars().boxed()
.collect(Collectors.toSet());
Map<Integer, Character> map = IntStream.range(0, string.length()).boxed()
.collect(Collectors.toMap(Function.identity(),
string::charAt));
</code></pre>
Pretty easy.<p>Now if we want them to be sorted:<p><pre><code> SortedSet<Integer> literal = new TreeSet<>(Set.of(1, 2, 3));
SortedSet<Integer> collected = string.chars().boxed()
.collect(Collectors.toCollection(TreeSet::new));
SortedMap<Integer, Character> map = IntStream.range(0, string.length()).boxed()
.collect(Collectors.toMap(Function.identity(),
string::charAt,
(a, b) -> { throw new UnsupportedOperationException(); },
TreeMap::new));
</code></pre>
Firstly, there is no SortedSet.of, so we have to explicitly wrap a literal set in a concrete implementation of SortedSet; as well as being a little less pretty, this means there's no chance for the JDK to use efficient specialised implementations of small constant sets, as it can for unsorted sets, and lists. Secondly, there is no toSortedSet collector, so we have to use the generic collection collector, again with a concrete implementation of SortedSet. Thirdly, there is no toSortedMap collector, so we have to use the generic toMap collector which again takes a concrete map implementation, but also requires us to handle key collisions ourself; this last point is particularly bad, because it is impossible to write a handler which is as good as the one used by the default toMap, because the handler doesn't get to see the colliding key!<p>What if we want them to be sorted in reverse?<p><pre><code> SortedSet<Integer> literal = new TreeSet<>(Comparator.reverseOrder());
literal.addAll(Set.of(1, 2, 3));
SortedSet<Integer> collected = string.chars().boxed()
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.reverseOrder())));
SortedMap<Integer, Character> map = IntStream.range(0, string.length()).boxed()
.collect(Collectors.toMap(Function.identity(),
string::charAt,
(a, b) -> { throw new UnsupportedOperationException(); },
() -> new TreeMap<>(Comparator.reverseOrder())));
</code></pre>
Where we use a collector, we have to expand the map implementation constructor method reference to a lambda, so we can pass in a comparator; mildly annoying but not too bad. But for the literal, there is no constructor which takes both a comparator and elements, so we have to create the set with the comparator, and then add the elements! It becomes impossible to do this in a single expression, or to use an immutable collection, and it's several times the volume of code as the unsorted case.<p>If you want to write the reverse sorted literal as an expression, you can do this:<p><pre><code> SortedSet<Integer> literal = Stream.of(1, 2, 3)
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.reverseOrder())));
</code></pre>
Which is still embarrassing.