Hi Stuart Will EnumSet also be updated to implement ReversibleSet? Or will it be updated to implement NavigableSet [1] independently of this enhancement?
I'd also like to propose adding `ReversibleSet::of` factory methods. This is something I miss having on SortedSet occasionally, but ReversibleSet would actually be a better fit for such methods. Kind regards, Anthony [1] https://bugs.openjdk.java.net/browse/JDK-6278287 On Friday, April 16, 2021 19:40 CEST, Stuart Marks <stuart.ma...@oracle.com> wrote: > This is a proposal to add a ReversibleCollection interface to the Collections > Framework. I'm looking for comments on overall design before I work on > detailed > specifications and tests. Please send such comments as replies on this email > thread. > > Here's a link to a draft PR that contains the code diffs. It's prototype > quality, > but it should be good enough to build and try out: > > https://github.com/openjdk/jdk/pull/3533 > > And here's a link to a class diagram showing the proposed additions: > > > https://cr.openjdk.java.net/~smarks/ReversibleCollection/ReversibleCollectionDiagram.pdf > > Thanks, > > s'marks > > > # Ordering and Reversibility > > > A long-standing concept that's been missing from collections is that of the > positioning, sequencing, or arrangement of elements as a structural property > of a > collection. (This is sometimes called the "iteration order" of a collection.) > For > example, a HashSet is not ordered, but a List is. This concept is mostly not > manifested in the collections API. > > Iterating a collection produces elements one after another in *some* > sequence. The > concept of "ordered" determines whether this sequence is defined or whether > it's a > coincidence of implementation. What does "having an order" mean? It implies > that > there is a first element and that each element has a successor. Since > collections > have a finite number of elements, it further implies that there is a last > element > that has no successor. However, it is difficult to discern whether a > collection has > a defined order. HashSet generally iterates its elements in the same undefined > order, and you can't actually tell that it's not a defined order. > > Streams do have a notion of ordering ("encounter order") and this is > preserved, > where appropriate, through the stream pipeline. It's possible to detect this > by > testing whether its spliterator has the ORDERED characteristic. Any > collection with > a defined order will have a spliterator with this characteristic. However, > this is > quite a roundabout way to determine whether a collection has a defined order. > Furthermore, knowing this doesn't enable any additional operations. It only > provides > constraints on the stream's implementations (keeping the elements in order) > and > provides stronger semantics for certain operations. For example, findFirst() > on an > unordered stream is the same as findAny(), but actually finds the first > element if > the stream is ordered. > > The concept of ordering is thus present in the system but is surfaced only in > a > fairly indirect way. We can strengthen abstraction of ordering by making a few > observations and considering their implications. > > Given that there is a first element and a last element, the sequence of > elements has > two ends. It's reasonable to consider operations (add, get, remove) on either > end. > Indeed, the Deque interface has a full complement of operations at each end. > This is > an oft-requested feature on various other collections. > > Each element except for the last has a successor, implying that each element > except > for the first has a predecessor. Thus it's reasonable to consider iterating > the > elements from first to last or from last to first (that is, in forward or > reverse > order). Indeed, the concept of iterating in reverse order appears already in > bits > and pieces in particular places around the collections: > > - List has indexOf() and lastIndexOf() > - Deque has removeFirstOccurrence() and removeLastOccurrence() > - List has a ListIterator with hasPrevious/previous methods > - Deque and NavigableSet have descendingIterator methods > > Given an ordered collection, though, there's no general way to iterate it in > reverse > order. Reversed iteration isn't the most common operation, but there are some > frequent use cases, such as operating on elements in most-recently-added > order. > Questions and bug reports about this have come up repeatedly over the years. > > Unfortunately, iterating in reverse order is much harder than iterating in > forward > order. There are a variety of ways to iterate in forward order. For example, > given a > List, one can do any of the following: > > for (var e : list) { ... } > list.forEach(...) > list.stream() > list.toArray() > > However, to iterate a list in reverse order, one must use an explicit loop > over > ListIterator: > > for (var it = list.listIterator(list.size()); it.hasPrevious(); ) { > var e = it.previous(); > ... > } > > Streaming the elements of a List in reverse order is even worse. One approach > would > be to implement a reverse-ordered Iterator that wraps a ListIterator and > delegates > hasNext/next calls to the ListIterator's hasPrevious/previous methods. Then, > this > Iterator can be turned into a Spliterator, which is then turned into a > Stream. (The > code to do this is left as an exercise for the reader.) Obtaining the > elements in > reverse via other means -- forEach() or toArray() -- is similarly difficult. > > Obtaining the elements in reverse order can be accomplished by adopting a > concept > from NavigableSet's descendingSet() method, which provides a reverse-ordered > view > collection. This view is also a NavigableSet, which means that any operation > that > can be performed on the original set can also be applied to the > reverse-ordered > view. If it were possible to obtain a similar reverse-ordered view on any > kind of > ordered collection, this would enable the elements to be processed in reverse > order > in any fashion, not just special-purposes APIs such as ListIterator or > descendingIterator(). > > > # LinkedHashSet > > > The main feature of LinkedHashSet is that it does have a defined ordering, as > opposed to HashSet, which does not. LinkedHashSet clearly has a first and a > last > element. However, LinkedHashSet is deficient in a number of ways: > > - access to and removal of the first element is reasonable (using an > iterator) but > it is not possible to add at the front > > - it is possible to add an element at the end, but access to and removal of > the > last element are expensive > > - it's not possible to iterate in reverse without copying the entire > collection > > Most frustratingly, LinkedHashSet is implemented using a doubly-linked list, > so > there is no fundamental implementation reason that prevents these operations > from > being supported. The main reason these operations aren't supported is > probably that > there hasn't been a good place to push such APIs. > > > # Proposal > > > Introduce a new ReversibleCollection<E> interface. This provides a new method > to > obtain a reverse-ordered view. It also contains several methods (copied from > Deque) > that allow operating on elements at both ends. > > > interface ReversibleCollection<E> extends Collection<E> { > ReversibleCollection<E> reversed(); > default void addFirst(E e) > default void addLast(E e) > default E getFirst() > default E getLast() > default E removeFirst() > default E removeLast() > } > > > The List, Deque, and SortedSet interfaces, and the LinkedHashSet class are > retrofitted to implement ReversibleCollection. They provide covariant > overriding > implementations of the reversed() method. For example, List.reversed() > returns a > List. Default implementations for all of these methods are provided in the > appropriate interfaces. > > Covariant overrides are also provided in several other classes. This presents > a > difficulty for LinkedHashSet, as there's no suitable return type for > reversed(). To > remedy this, we add another interface > > > interface ReversibleSet<E> extends ReversibleCollection<E>, Set<E> { } > > > This adds no new methods but is essentially an intersection of > ReversibleCollection > and Set. As such, it's suitable as the return type of > LinkedHashSet.reversed() and > the set views of LinkedHashMap. > > SortedSet::addFirst and addLast throw UnsupportedOperationException. This is > because > SortedSet's ordering is determined by the comparison method and cannot be > controlled > explicitly. > > LinkedHashSet::addFirst and addLast add the element at the appropriate > position or > reposition the element if it is already present in the set. > > New methods are added to LinkedHashMap > > V putFirst(K, V) > V putLast(K, V) > > which put the mapping at the designated position or reposition the mapping if > is > already present in the map. > > > # Design & Implementation Issues > > > Covariant overrides for reversed() are provided where possible. However, > there is a > conflict between List and Deque, as there are examples in the JDK (such as > LinkedList) that implement both List and Deque. Since List is much more > popular than > Deque, I've decided against adding a covariant override to Deque. Instead, a > method > Deque::reversedDeque is added that returns a Deque, and Deque::reversed > returns > ReversibleCollection. > > There is no ReversibleMap interface. Most iteration over maps is over the > entrySet, > keySet, or values views. NavigableMap already has descendingMap(), and > LinkedHashMap > provides ReversibleX views, which cover most of the cases already. > > Introducing new methods into an interface hierarchy always raises the > possibility of > name clashes. I've done some searching but I haven't found any major issues, > but we > should be on the lookout for this. I'll continue to do more searching for such > conflicts. > > Introducing covariant overrides on existing methods (notably LinkedHashMap > entrySet, > keySet, and values) could present incompatibility issues with subclasses that > override these methods. I've done some searching and again I haven't found > major > problems, but this is nonetheless a point of concern. I'll do more searching > here, too. > > The default implementations for List::reversed, Deque::reversed, and > SortedSet::reversed return reverse-ordered views that are implemented using > only > public methods of the original interface. This demonstrates the feasibility of > retrofitting reverse ordering onto any instance of these interfaces. > Similarly, the > various add/get/remove methods' default implementations are all implementable > in > terms of an iterator or reverse-ordered iterator. That said, some concurrent > collection implementations will need to override these default > implementations in > order to preserve their safety invariants. It would probably also be wise to > add > optimized implementations to the more commonly-used collection > implementations. > > Given that a ReversibleCollection has a defined ordering, any spliterator > obtained > from such a collection should have the Spliterator.ORDERED characteristic. > > This proposal is related to a previous discussion on a similar topic, where > Tagir > Valeev had proposed OrderedSet and OrderedMap: > > > http://mail.openjdk.java.net/pipermail/core-libs-dev/2020-April/066028.html > > I think there were some good ideas in there and in the ensuing discussion, > but it > didn't go anywhere. Several of the concepts in this proposal are an evolution > of > some ideas discussed in that thread. > > One of the ideas from that thread was to use Deque as a common interface for > many > collections to support reversibility and operations at the ends. We've > abandoned > that idea in this proposal. The reasons are that it's cluttered with a bunch > of > methods that are inherited from Queue. Also, some methods are null-returning > instead > of throwing, which introduces confusion for collections that can contain > nulls. > > # END