Well, what’s the “performance benefit of addAll
”? In the end, addAll
has to add all elements to the target Collection
anyway. In case, the target is an ArrayList
, the main benefit is to ensure that there are no unnecessary capacity increase operations.
But note that this happens at the expense of creating a temporary array, see implementation of ArrayList.addAll
. To outweigh this expense, you have to add a significant number of elements.
If we are going to add more elements than the target’s current capacity, an increase operation is unavoidable. So addAll
offers only a benefit, if the target has to increase the capacity more than once if you simply use add
. Since the capacity is raised by factor 1.5 and the capacity is equal or higher than the current size, we have to add at least more elements than half of it’s current size to expect an unnecessary capacity increase operation.
If you really think, this is going to be an issue, it’s easy to fix:
if(target instanceof ArrayList)
((ArrayList)target).ensureCapacity(target.size()+source.size());
source.stream().map(String::toLowerCase).forEachOrdered(target::add);
Of course, there are a few corner cases where the costs of add
are much higher, e.g. CopyOnWriteArrayList
. For this target collection type, collecting into a List
via collect(Collectors.toList())
first, followed by addAll
might be beneficial. Or you create a simple lazy Collection
as intermediate step:
public static <T> Collection<T> lazyCollection(Supplier<? extends Stream<T>> s) {
return new AbstractCollection<T>() {
public Iterator<T> iterator() { return s.get().iterator(); }
public int size() { return (int)s.get().count(); }
public Object[] toArray() { return s.get().toArray(); }
};
}
which can be used like:
target.addAll(lazyCollection(() -> source.stream().map(String::toLowerCase)));
This approach would suffer from evaluating the Stream twice, if a collection asks for size()
first, before acquiring an Iterator
, but afaik, no standard collection does. They either, use the iterator without relying on a predicted size, or resort to toArray()
, like ArrayList.addAll
or CopyOnWriteArrayList.addAll
do.