I'm still with Martin on this. It makes no sense to me to allow
replacing one element to not cause CME, but replacing more than one does
cause CME. This is inconsistent and confusing and the end result is the
programmer won't know what to expect when or why.
The original intent of CME, from my recollections back in
lead-up-to-Java-5 days, was to prevent iterators from breaking i.e.
throwing exceptions, due to the occurrence of the "concurrent" operation
that changed the structure. It was not intended as an indicator of a
semantic programming error. Replacing one element whilst there is a live
iterator can be just as semantically wrong as replacing them all.
Cheers,
David
-----
On 16/05/2018 10:34 AM, Stuart Marks wrote:
(TL;DR - replaceAll incrementing modCount is a bug.)
I acknowledge that statement is the one in dispute.
Hmmm ... my previous convincing arguments have failed to convince ?!
Your argument above applies to List.set just as much as
List.repladeAll, because the latter is nothing more semantically than
a bunch of calls to the former. They should have the same behavior.
Not having the same behavior leads to inconsistency, seen today in
subList operations on ArrayList and Vector having different modCount
behavior than on the root list.
Right, I read those arguments, and I'm not convinced.
Just because an individual operation has some characteristic doesn't
necessarily imply that an aggregate operation must have the same
characteristic. (This is variously called a fallacy of composition, or
in U.S. tax law, the step transaction doctrine.)
Bringing replaceAll() to the public API exposes it as a single
operation, with its own semantics. Note that Vector.replaceAll() holds
the lock for the entire operation, not merely around individual set()
operations. It really is different from performing individual set()
operations.
Again, imagine this use case: there is a periodic background task that
optimizes all the elements of a Vector
vector.replaceAll(x -> optimized(x))
That should not break any iterations in progress.
I don't think it's possible to do that correctly without holding a lock
around the entire iteration. If the lock is held, CME can't occur, as a
concurrent replaceAll() will occur before or after the iteration, never
in the middle.
If an iteration over a Vector doesn't hold a lock, any read-modify-write
operations (consider a loop with a ListIterator on which set() is
called) can be interleaved with bulk operations (like replaceAll) which
is clearly incorrect. In such cases, CME should be thrown.
Also, this use case cannot be written today, because CME is thrown. I'm
not aware of an actual use case that's been prevented because CME is
thrown. Of course, as API designers we have to anticipate needs and not
just react to complaints. My view of this kind of situation
(interleaving of bulk operation(s) with an iteration loop) is much more
likely to be a programming error than an actual use case.
To strengthen that, the default method List.replaceAll is specified to
be equivalent to
final ListIterator<E> li = list.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
and incrementing modCount breaks "equivalent to".
Note carefully: that is an "Implementation Requirement" on the default
implementation of List.replaceAll(). It doesn't govern implementations
in implementing classes such as ArrayList.
s'marks