Skip to content

Make Lists.transform() views delegate spliterator()/forEach() to the backing list - #8589

Open
pangwangshu wants to merge 3 commits into
google:masterfrom
pangwangshu:wapang/issue8165
Open

Make Lists.transform() views delegate spliterator()/forEach() to the backing list#8589
pangwangshu wants to merge 3 commits into
google:masterfrom
pangwangshu:wapang/issue8165

Conversation

@pangwangshu

Copy link
Copy Markdown

Fixes #8165

Problem

Lists.transform() returns a view backed by TransformingRandomAccessList
when the input list implements RandomAccess. That class didn't override
spliterator(), so it inherited 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 is
documented 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
ConcurrentModificationException from
AbstractList$RandomAccessSpliterator.get, even though
CopyOnWriteArrayList itself guarantees it never throws that exception.

Fix

TransformingRandomAccessList now overrides:

  • spliterator() — delegates to fromList.spliterator() via
    CollectSpliterators.map(), so the transformed view's spliterator
    inherits 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 to fromList.forEach(...), for the
    same reason.

TransformingSequentialList (the non-RandomAccess sibling) isn't
affected: it never falls back to the JDK's RandomAccessSpliterator in
the first place.

This is a behavioral bug fix only — no public API signatures change.

Testing

Added tests to ListsTest covering: the reported
ConcurrentModificationException scenario against a
CopyOnWriteArrayList, spliterator-characteristics propagation from the
backing list, and baseline spliterator()/forEach() behavior on a plain
RandomAccess list. Ran the full build locally
(./mvnw clean install -Dtest=ListsTest -Dsurefire.failIfNoSpecifiedTests=false)
— all tests pass, including the GWT-compatible-libs module.

@google-cla

google-cla Bot commented Aug 4, 2026

Copy link
Copy Markdown

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
@kojallipalli2210-alt

Copy link
Copy Markdown

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
current transformed IMMUTABLE=false
fixed transformed IMMUTABLE=true

Adding the same delegation to TransformingSequentialList fixes that case:

@OverRide
@GwtIncompatible // Spliterator
public Spliterator spliterator() {
return CollectSpliterators.map(fromList.spliterator(), 0, function);
}

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lists::transform and probably others do not produce spliterators/iterators with correct characteristics

2 participants