You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AbstractMapMultiSet's uniqueSet() and entrySet() view iterators drop the whole entry from the backing map but never decrement the cached size field, so removing an element with N copies through either view leaves size() == N while isEmpty() is true and toArray() (sized by size()) returns N trailing nulls.
UniqueSetIterator.remove() calls super.remove() (which deletes the key) and then parent.remove(lastElement, count), but that call short-circuits at map.get(...) == null and never adjusts size; EntrySetIterator.remove() calls decorated.remove() and never touches size at all. Both overrides now subtract the removed entry count from size and bump modCount, matching the size-correct AbstractMapBag.BagIterator and remove(Object, int). Found by comparing the two map-backed count collections. HashMultiSet and TreeMultiSet inherit the fix.
I used AI to create any part of, or all of, this pull request. Which AI tool was used to create this pull request, and to what extent did it contribute?
Run a successful build using the default Maven goal with mvn; that's mvn on the command line by itself.
Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best practice.
Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
Each commit in the pull request should have a meaningful subject line and body. Note that a maintainer may squash commits during the merge process.
garydgregory
changed the title
fix stale size in AbstractMapMultiSet view iterator remove
Fix stale size in AbstractMapMultiSet view iterator remove
Jul 2, 2026
Please read the MultiSet contract and it seems like this PR would break it. WDYT?
This PR changes a main abstract class but only tests a single implementation. If this PR is an actual fix (see above), we need to test all concrete subclasses.
TY!
The removal behavior is unchanged and still follows the contract. uniqueSet() and entrySet() removal drops the whole entry (all occurrences of the element), exactly as before. The patch only fixes the cached size field, which those two view iterators never decremented. That left size() reporting N while isEmpty() returned true and toArray() (sized by size()) came back with N trailing nulls, so size() was inconsistent with the other views. Found it comparing this class against AbstractMapBag, whose bag iterator already adjusts size.
On testing: moved the regression test up into AbstractMultiSetTest so it runs for every concrete implementation (HashMultiSet, PredicatedMultiSet, SynchronizedMultiSet; skipped for UnmodifiableMultiSet, which does not support removal) instead of just HashMultiSet.
Thank you @rootvector2 , merged 🚀 Note that I added 2 assertions to check that remove() can only be called once per next().
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AbstractMapMultiSet'suniqueSet()andentrySet()view iterators drop the whole entry from the backing map but never decrement the cachedsizefield, so removing an element withNcopies through either view leavessize() == NwhileisEmpty()istrueandtoArray()(sized bysize()) returnsNtrailing nulls.UniqueSetIterator.remove()callssuper.remove()(which deletes the key) and thenparent.remove(lastElement, count), but that call short-circuits atmap.get(...) == nulland never adjustssize;EntrySetIterator.remove()callsdecorated.remove()and never touchessizeat all. Both overrides now subtract the removed entry count fromsizeand bumpmodCount, matching the size-correctAbstractMapBag.BagIteratorandremove(Object, int). Found by comparing the two map-backed count collections.HashMultiSetandTreeMultiSetinherit the fix.Repro before the patch:
mvn; that'smvnon the command line by itself.