Make Lists.transform() views delegate spliterator()/forEach() to the backing list - #8589
Make Lists.transform() views delegate spliterator()/forEach() to the backing list#8589pangwangshu wants to merge 3 commits into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
…backing list TransformingRandomAccessList previously relied on the JDK's default List.spliterator(), which for RandomAccess lists falls back to AbstractList.RandomAccessSpliterator. That implementation is index-based and translates concurrent structural changes into a ConcurrentModificationException, even when the backing list (e.g. CopyOnWriteArrayList) is documented to never throw one. Override spliterator() to delegate to the backing list's own spliterator via CollectSpliterators.map(), and forEach() to delegate directly to the backing list's forEach(), so transformed views inherit the concurrency and other characteristics of their source list. Fixes google#8165
a875fdd to
81e2735
Compare
|
I independently checked the non-RandomAccess case against the JDK 21 List.spliterator() default and found one gap that may be worth covering before #8589 lands. TransformingSequentialList also drops specialized spliterator characteristics from its backing list. For non-RandomAccess lists, the JDK default creates a generic iterator-backed spliterator instead of delegating to the backing list's spliterator(). So the sequential variant avoids the specific AbstractList.RandomAccessSpliterator CME path, but it can still lose characteristics covered by #8165. I reproduced this locally on OpenJDK 21.0.11 with a non-RandomAccess list whose specialized spliterator delegates to CopyOnWriteArrayList: source IMMUTABLE=true Adding the same delegation to TransformingSequentialList fixes that case: @OverRide CollectSpliterators.map(..., 0, ...) already limits the inherited characteristics appropriately for an arbitrary mapping. This is also consistent with the earlier #8170 approach, which added the override to both transformed-list implementations. A focused regression test can use a non-RandomAccess ForwardingList backed by CopyOnWriteArrayList, override its spliterator() to delegate to the backing list, and assert that the transformed view retains Spliterator.IMMUTABLE. |
Fixes #8165
Problem
Lists.transform()returns a view backed byTransformingRandomAccessListwhen the input list implements
RandomAccess. That class didn't overridespliterator(), so it inherited the JDK's defaultList.spliterator(),which for
RandomAccesslists falls back toAbstractList.RandomAccessSpliterator. That implementation is index-basedand translates concurrent structural changes into a
ConcurrentModificationException— even when the backing list isdocumented to never throw one (e.g.
CopyOnWriteArrayList).As reported in #8165, streaming over
Lists.transform(coWriteArrayList, fn)while the backing list is concurrently mutated throws
ConcurrentModificationExceptionfromAbstractList$RandomAccessSpliterator.get, even thoughCopyOnWriteArrayListitself guarantees it never throws that exception.Fix
TransformingRandomAccessListnow overrides:spliterator()— delegates tofromList.spliterator()viaCollectSpliterators.map(), so the transformed view's spliteratorinherits the backing list's actual splitting and concurrency behavior
instead of falling back to the generic index-based one. This also fixes
the transformed view's spliterator not reporting characteristics (e.g.
IMMUTABLE) that the backing list's spliterator has.forEach()— delegates straight tofromList.forEach(...), for thesame reason.
TransformingSequentialList(the non-RandomAccesssibling) isn'taffected: it never falls back to the JDK's
RandomAccessSpliteratorinthe first place.
This is a behavioral bug fix only — no public API signatures change.
Testing
Added tests to
ListsTestcovering: the reportedConcurrentModificationExceptionscenario against aCopyOnWriteArrayList, spliterator-characteristics propagation from thebacking list, and baseline
spliterator()/forEach()behavior on a plainRandomAccesslist. Ran the full build locally(
./mvnw clean install -Dtest=ListsTest -Dsurefire.failIfNoSpecifiedTests=false)— all tests pass, including the GWT-compatible-libs module.