I’m watching the latest JEP Café video from my colleague José Paumard, where he talks about the Comparator
interface. One of the things you can do with a Comparator
is to use it to sort a list:
list.sort(comparator);
If a class implements the Comparable
interface, that means instances of that class know how to compare themselves. (The “comparison of themselves” is referred to as the natural order.) If the list contains elements that are Comparable
, you don’t need to pass a Comparator
argument to the List.sort()
method. Instead, you pass null:
list.sort(null);
and the list will be sorted according to the elements’ natural order. José quite reasonably observes that it’s somewhat unpleasant to have to pass “null” there, and he suggests that it would be cleaner to have a no-arg overload that sorts the list in natural order.
Yeah, we should add that!
After all, there are two overloads of the Stream.sorted()
method: a no-arg overload that sorts by natural order, and an overload that takes a Comparator
argument. We should clearly do the same for List.sort()
. Maybe its omission was just an oversight. On the other hand, it’s such an obvious thing; maybe the omission was deliberate.
What about compatibility?
The default methods feature was added in Java 8. This allowed the addition of new methods to interfaces. Prior to Java 8, adding a method to an interface was an incompatible change. With default methods, it’s possible to add a new method in a compatible way. However, incompatibility is still possible when adding a default method. For example, consider the List.sort(Comparator)
method again. Its return type is void
. Suppose there is a Java 7 application that has this List
implementation:
class MyList<E> extends List<E> {
public MyList<E> sort(Comparator<? super E> comparator) {
// sort the list
return this;
}
}
If this class were recompiled on Java 8, an error would occur, because Java doesn’t allow overrides with a differing return type.
(Java does allow covariant return types in an overriding method, where the return type of the override is a subtype of the return type of the overridden method. That doesn’t apply here, because MyList<E>
isn’t a subtype of void
.)
Adding a default method to an interface can be compatible, but it might be incompatible if there is a conflict with a method in an existing class. It’s therefore necessary to be quite careful when adding default methods to interfaces. It’s even more important to be careful if the interface is widely implemented (like List
), if the method name is short and common (like sort
), and if it has few or no arguments. Thus, it seems likely that adding a no-arg List.sort()
default method would conflict with a method in an existing subclass. Maybe adding this default method isn’t such a good idea after all.
This line of reasoning seems valuable. Maybe I should write it down somewhere!
This issue is fairly subtle, and it’s worth writing down so that somebody in the future doesn’t make a mistake. A blog post (like this one) is one way to preserve this information. However, this blog isn’t connected to the OpenJDK project, so somebody working on the JDK wouldn’t know to search here. Someplace closer to the JDK would be preferable.
Another place to store this information is the JDK Bug System (JBS), which is the bug database for the JDK. It contains a lot of history, including bug reports converted from the old Sun bug database dating back to the pre-JDK-1.0 era in the 1990s. It seems likely that information in JBS will persist longer than this blog. Since JBS is associated with the JDK project, it’s also more likely that somebody working on the JDK will find it. Plus, JBS is a database, with nice categories and querying capabilities, making it easy to find information.
How should this kind of information be recorded in a bug database? I could file a request to add this API, close it out, and put the rationale for not implementing it into the comments. Filing a request and closing it immediately might seem excessively fussy. Once it’s in the database, though, it would be easy for future maintainers to rediscover the request in the future if a similar issue were to arise.
Before filing a new issue, it’s always good practice to search the database to see if something similar exists already. Indeed, upon searching, I found this:
JDK-8078076 Create Overload List#sort() for Natural Ordering Case
Huh. That seems like it covers exactly the same issue. It was submitted in April 2015 by “Webbug Group”, which is the JBS username that’s used when a bug is received from an anonymous person on the internet. The bug’s status is Closed, and its resolution value is Won’t Fix. Who did that and why? Looking through bug report, the last comment (also from April 2015) is this one:
This was considered and rejected during JDK 8 development. We were fairly minimal with the addition of default methods. There have already been incompatibilities with the List.sort(Comparator) method; adding a no-arg List.sort() method would likely cause additional incompatibilities while adding very little value. Closing as Won’t Fix.
This comment was written by … me! Wow, I had completely forgotten about this. Not surprisingly, it turns out that the bug database has a better memory than I do. I just went through this line of reasoning and reached a conclusion. Then I found the same line of reasoning and the same conclusion that I had written down nearly eight years earlier. Fortunately, present me agrees with past me.
Suppose that I had watched José’s video and immediately decided to implement the new default method. Every change to the JDK requires a JBS entry, so I would have started by searching for an existing issue or filing a new one. It seems likely I would have run across the 2015 issue at that time. (There are only 14 collections bugs in the database that have both “list” and “sort” in the title.) Even if I had missed it, one of the reviewers of the change probably would have noticed the 2015 bug and called my attention to it. Either way, it’s clear that writing down the reasoning in 2015 is valuable to a future maintainer in 2023, whether that maintainer is me or somebody else. And it seems likely that having this issue in the database, along with other similar issues, will be of value to future maintainers.
Anyway, sorry about that José, that’s why we won’t be adding a no-arg List.sort()
overload.
[…] >> The Importance of Writing Stuff Down [stuartmarks.com] […]
I read this JEP 431 Motivation (https://openjdk.org/jeps/431)
and it says that “A sequenced collection has first and last elements, and the elements between them have successors and predecessors.” but I didn’t see any mention to have access to the successor and predecessor elements of the current element in an Iterator. Sometimes, I face the necessity to have some business code that deals with calculations of the current element and the predecessor or the successor.
Something like that:
SequencedCollection sensorRecords = …
Iterator iterator = sensorRecords.iterator();
while(iterator.hasNext()) {
SensorRecord currentValue = iterator.next();
SensorRecord predecessorOfTheCurrentValue = iterator.predecessor();
SensorRecord successorOfTheCurrentValue = iterator.successor();
}
ListIterator has a method called previous that returns the previous element but it moves the cursor position as well. The idea of having access to the predecessor and successor would be to get those values without moving the cursor position of the iterator.
Does that make sense to you?
Right, the idea of SequencedCollection elements having successors and predecessors is mostly for definitional purposes. There aren’t any such operations in the APIs, as you noted.
You might look at NavigableSet, which has higher(E) and lower(E) methods that let you find the adjacent elements. NavigableMap has similar methods for finding adjacent keys and entries, given a key. Using these APIs you can “iterate” through a NavigableSet or NavigableMap without using an explicit Iterator object.