The behaviour is (somewhat) documented in the javadoc:
This implementation determines which is the smaller of this set and the specified collection, by invoking the size method on each. If this set has fewer elements, then the implementation iterates over this set, checking each element returned by the iterator in turn to see if it is contained in the specified collection. If it is so contained, it is removed from this set with the iterator's remove method. If the specified collection has fewer elements, then the implementation iterates over the specified collection, removing from this set each element returned by the iterator, using this set's remove method.
What this means in practice, when you call source.removeAll(removals);
:
if the removals
collection is of a smaller size than source
, the remove
method of HashSet
is called, which is fast.
if the removals
collection is of equal or larger size than the source
, then removals.contains
is called, which is slow for an ArrayList.
Quick fix:
Collection<Integer> removals = new HashSet<Integer>();
Note that there is an open bug that is very similar to what you describe. The bottom line seems to be that it is probably a poor choice but can't be changed because it is documented in the javadoc.
For reference, this is the code of removeAll
(in Java 8 - haven't checked other versions):
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
if (size() > c.size()) {
for (Iterator<?> i = c.iterator(); i.hasNext(); )
modified |= remove(i.next());
} else {
for (Iterator<?> i = iterator(); i.hasNext(); ) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
}
return modified;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With