Change RealMultibinder#doProvision to use ImmutableSet#buildWithExpectedSize, and avoid an extra T[] array allocation when permitDuplicates==true.

Since the normal usage should be that the items in the values array are all unique, and ImmutableSet#copyOf will create an initial list of max(4, sqrt(length)) if length>4, but then grows that list to length (or larger) if the array was all unique (and then resize is back down during build()).

While here, the T[] values array is also only needed to be filled in when permitDuplicates==false (for the error message if duplicates were detected).

PiperOrigin-RevId: 597244931
This commit is contained in:
Guice Team
2024-01-10 07:34:13 -08:00
committed by Guice Team
parent 37b7b7e9f5
commit 21d26cfc65
@@ -236,21 +236,33 @@ public final class RealMultibinder<T> implements Module {
// if localInjectors == null, then we have no bindings so return the empty set.
return ImmutableSet.of();
}
// Ideally we would just add to an ImmutableSet.Builder, but if we did that and there were
// duplicates we wouldn't be able to tell which one was the duplicate. So to manage this we
// first put everything into an array and then construct the set. This way if something gets
// dropped we can figure out what it is.
// If duplicates aren't permitted, we need to capture the original values in order to show a
// meaningful error message to users (if duplicates were encountered).
@SuppressWarnings("unchecked")
T[] values = (T[]) new Object[localInjectors.length];
T[] values = !permitDuplicates ? (T[]) new Object[localInjectors.length] : null;
// Avoid ImmutableSet.copyOf(T[]), because it assumes there'll be duplicates in the input, but
// in the usual case of permitDuplicates==false, we know the exact size must be
// `localInjector.length` (otherwise we fail). This uses `builderWithExpectedSize` to avoid
// the overhead of copyOf or an unknown builder size. If permitDuplicates==true, this will
// assume a potentially larger size (but never a smaller size), and `build` will then reduce
// as necessary.
ImmutableSet.Builder<T> setBuilder =
ImmutableSet.<T>builderWithExpectedSize(localInjectors.length);
for (int i = 0; i < localInjectors.length; i++) {
SingleParameterInjector<T> parameterInjector = localInjectors[i];
T newValue = parameterInjector.inject(context);
if (newValue == null) {
throw newNullEntryException(i);
}
values[i] = newValue;
if (!permitDuplicates) {
values[i] = newValue;
}
setBuilder.add(newValue);
}
ImmutableSet<T> set = ImmutableSet.copyOf(values);
ImmutableSet<T> set = setBuilder.build();
// There are fewer items in the set than the array. Figure out which one got dropped.
if (!permitDuplicates && set.size() < values.length) {
throw newDuplicateValuesException(values);