It sounds like you're after some generalized notion of "consistency", and the fact
that offer*() return a boolean whereas add*() do not seems inconsistent.
Unfortunately, the methods have different semantics. After add(obj), obj is *always*
a member of the collection, whereas after offer*(obj), obj *might or might not* be a
member of the collection. The semantics of addFirst/addLast are more closely aligned
with those of the add*() methods on Collection, so I decided to promote
addFirst/addLast to SequencedCollection instead of offerFirst/offerLast.
On your other point, if you want to compare elements of a SequencedCollection to
those of another, in encounter order, you can just use regular Iterators for that.
ListIterator isn't necessary.
s'marks
On 10/5/22 3:36 PM, Ernie Rael wrote:
On 10/5/22 9:34 AM, Stuart Marks wrote:
On 10/4/22 9:38 PM, Ernie Rael wrote:
Summary of key points (maybe the mail was TL;DR)
OK thanks, I was still mulling over the previous email wondering which parts were
significant enough to reply to.
* SequencedCollection methods addFirst,addLast are the only methods in
Collection hierarchy (AFAIK) that might modify the collection and do
not return/signal if the collection was modified. Seems like
offerFirst,offerLast are more consistent with Collections and still
avoid method proliferation.
The problem is that the boolean return values from add() and from offerX() mean
different things, and having them be adjacent on List would be confusing. (Yes,
they're both present on Deque, which is one of the reasons Deque is too
complicated.) And we couldn't adjust the semantics of SequencedCollection.offerX()
to match add(), as that would clash with the existing semantics on Deque.
It is not uncommon for a sub-interface/sub-class to clarify the precise meaning of a
method. If there's a general definition of SequencedCollection.offerFirst(e), then
the Deque definition clarifies the meaning in that context. Consider:
Collections.add(e) - Returns true if this collection changed as a result of the
call
List.add(e) - Return true
Set.add(e) - Returns true if this set did not already contain the specified
element
From your other messages it seems like you want the boolean return value in order
to keep track of whether the collection changed such that it affects equals() and
hashCode().
No, I was just discussing the relationship of change and equals() when working with
a SequencedCollection; it's more observations around using SeqCol. It's interesting
that an addAll() can permute the structure, and end up at the same place.
There are other methods that might modify collections where you can't tell whether
they actually modified the collection; consider Collection::clear or
List::replaceAll.
I'll be more precise: methods that work with a single item return/signal change;
most bulk operators such as removeif(), retainAll(), removeAll(), addAll() also
return/signal change.
My main point is that "void SequencedCollection.addFirst(e)" is inconsistent with
Collections' design. clear() is, well, clear(). replaceAll() seems to be an
exception (a lone exception?).
So I don't think the boolean return value from add/offer is really buying you all
that much.
When I put together a class based on a Collection, I like to follow the general
design pattern. Not sure if/when I may have used the "return change" when using a
collection. But when sub-classing a collection, since everything does it, so do I;
I'll return change in any additional methods I might add. Consistent, least surprise...
* With LinkedHashSet, seems like listIterator is missing. Rather than
making that part of SequencedCollection, maybe an "interface
ListIterable { ListIterator listIterator(int index); }". In addition
to modification, bi-directional might also be optional, to support
it's use with list equals.
ListIterator has indexes and so it's pretty much tied to List. Maybe what you're
missing from LinkedHashSet
I want to be able to do List.equals(SequencedCollection) and vice versa (in
particular with LinkedHashSet). In the list equals() implementations I've looked at,
they all use two ListIterator to do the compare; only forward iteration. For
equals(), I think can wrap the SequencedCollection iterator in a forward,
uni-directional listIterator, a little messy, and use that for equals(); support
from the infrastructure would be nice. Which is where the idea of "ListIterator
Collections.listIterator(iterator, index)" in the other email comes from.
Some daydreaming: For equals(), indexes don't matter except for initialization. And
as far as "index ... tied to list", if SequencedCollection had a listIterator, I
think I could form sub-sequence from that, with only forward iteration. But
sub-SequencedCollection is a different topic. My usage requirement now is for equals().
Lists may include index, but they don't really depend on it unless they're
RandomAccess; consider LinkedList. I don't see how indexes have a bearing on this
discussion; in a listIterator the index is contained/maintained in the iterator.
is the ability to insert or reposition an element somewhere in the middle? This is
certainly a valid use case, but it's quite rare, and I'm not sure I'd want to
support it using an Iterator style mechanism anyway.
Surveying the usages of ListIterator,
The implicit use by equals() is missing.
I found that it's mainly used for iteration in reverse, and secondarily for
replacing all the elements of a List (somewhat supplanted by replaceAll). We did
run into one case where ListIterator was used to edit items within a list but it
turned out to be INCREDIBLY clumsy to use. So if we want to support that (fairly
rare) use case, I wouldn't start with a ListIterator or some variant with indexes
removed. I'd also want to see use cases before considering adding new mechanisms.
s'marks