Skip to content

Return null from Spliterator.getComparator() for natural ordering (fixes #6187)#8375

Open
holly-agyei wants to merge 2 commits intogoogle:masterfrom
holly-agyei:fix-6187-natural-order-spliterator
Open

Return null from Spliterator.getComparator() for natural ordering (fixes #6187)#8375
holly-agyei wants to merge 2 commits intogoogle:masterfrom
holly-agyei:fix-6187-natural-order-spliterator

Conversation

@holly-agyei
Copy link
Copy Markdown
Contributor

@holly-agyei holly-agyei commented Apr 24, 2026

Fixes #6187.

Problem

Per the Spliterator.getComparator() contract, implementations whose source is sorted in natural order must return null. The JDK uses this signal to clear the SORTED characteristic when the comparator is non-null (StreamOpFlag line 751-755), which in turn disables Stream.sorted()'s short-circuit optimization, causing unnecessary re-sorting of already-sorted data.

ImmutableSortedSet.spliterator() and CollectSpliterators.indexed() (used by ImmutableSortedAsList.spliterator(), which in turn backs RegularImmutableSortedSet.spliterator() and its subset views) returned Ordering.natural() instead of null for naturally-ordered sources, so code like:

ImmutableSortedSet.of(1, 2, 3, 4).stream().sorted().collect(toList());

incurred a full re-sort rather than the documented no-op.

Fix

  • Added CollectSpliterators.isNaturalOrder(Comparator), a shared helper that recognizes the two well-known natural-ordering singletons (Ordering.natural() and Comparator.naturalOrder()) as well as null.
  • CollectSpliterators.indexed() and ImmutableSortedSet.spliterator() now normalize natural-ordering comparators to null at the getComparator() boundary. Custom and reverse-order comparators are preserved unchanged, so DescendingImmutableSortedSet and user-supplied comparators are unaffected.

@kevinb9n called out special-casing Comparator.naturalOrder() in addition to Ordering.natural() on #6188 (the earlier, stale attempt by @kilink); this PR honors that and adds the test coverage that was missing from both #6188 and the withdrawn #8174.

Tests

Added regression tests at two levels:

CollectSpliteratorsTest

  • testIsNaturalOrder_null, _orderingNatural, _comparatorNaturalOrder — helper returns true for each natural-ordering form
  • testIsNaturalOrder_reverseOrder_false, _customComparator_false — non-natural comparators are rejected
  • testIndexedSortedSpliterator_naturalOrderReturnsNull, _comparatorNaturalOrderReturnsNull — indexed spliterator surfaces null for natural ordering and keeps SORTED
  • testIndexedSortedSpliterator_customComparatorIsPreserved — custom comparator round-trips unchanged

ImmutableSortedSetTest

  • testSpliteratorGetComparator_naturalOrdering_returnsNull
  • testSpliteratorGetComparator_comparatorNaturalOrder_returnsNull
  • testSpliteratorGetComparator_customOrdering_returnsComparator
  • testSpliteratorHasSortedCharacteristic
  • testAsListSpliteratorGetComparator_naturalOrdering_returnsNull (covers the ImmutableSortedAsList path used by RegularImmutableSortedSet)
  • testAsListSpliteratorGetComparator_customOrdering_returnsComparator

Test plan

  • ./mvnw -pl guava -am install -DskipTests — builds clean
  • ./mvnw -pl guava-tests test -Dtest='CollectSpliteratorsTest,ImmutableSortedSetTest' — 17,293 tests, 0 failures
  • Broader sweep: ./mvnw -pl guava-tests test -Dtest='*SortedSet*,*SortedMap*,*SortedMultiset*,*Collect*Test,ImmutableListTest,ImmutableSetTest,StreamsTest' — 455,720 tests, 0 failures

Per CONTRIBUTING.md, the android/ tree is left to the automatic mirror script.

Per the Spliterator.getComparator() contract, implementations whose source
is sorted in natural order must return null. The JDK uses this signal to
clear the SORTED characteristic when the comparator is non-null, which in
turn disables Stream.sorted()'s short-circuit optimization (SortedOps line
136-139), causing unnecessary re-sorting of already-sorted data.

ImmutableSortedSet.spliterator() and CollectSpliterators.indexed() (used
by ImmutableSortedAsList.spliterator()) returned Ordering.natural() instead
of null for naturally-ordered sources, so code like

    ImmutableSortedSet.of(1, 2, 3, 4).stream().sorted().collect(toList());

incurred a full re-sort rather than a no-op.

This change normalizes the two well-known natural-ordering singletons
(Ordering.natural() and Comparator.naturalOrder()) to null at the
getComparator() boundary via a shared CollectSpliterators.isNaturalOrder()
helper. Custom and reverse-order comparators are preserved unchanged.

Fixes google#6187.

Made-with: Cursor
@cgdecker cgdecker added status=triaged type=other Miscellaneous activities not covered by other type= labels P3 no SLO labels Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 no SLO status=triaged type=other Miscellaneous activities not covered by other type= labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spliterators for sorted collections cause inefficient stream operations when natural ordering is used

2 participants