Почему ImmutableList в Guava имеет так много перегруженных методов ()?
Я просто смотрел на гуаву ImmutableList
и я заметил, что of()
метод был перегружен 12 раз.
Мне кажется, все, что им нужно было:
static <E> ImmutableList<E> of();
static <E> ImmutableList<E> of(E element); // not even necessary
static <E> ImmutableList<E> of(E... elements);
В чем причина того, что у вас так много похожих вариантов?
3 ответа
Varargs и дженерики не очень хорошо играют вместе. Методы Varargs могут вызывать предупреждение с общими аргументами, а перегрузки предотвращают это предупреждение, за исключением редкого случая, когда вы хотите добавить более 11 элементов в неизменяемый список, используя of()
,
Комментарии в источнике говорят:
Они идут до одиннадцати. После этого вы просто получаете форму varargs и любые предупреждения, которые могут прийти вместе с ней.:(
Обратите внимание, что аннотация @SafeVarargs в Java 7 была добавлена специально для устранения необходимости такого рода вещей. Один of(E...)
метод аннотируется @SafeVarargs
может использоваться и не будет давать предупреждений с общими аргументами.
Есть и причина производительности. Каждый вызов метода varargs вызывает выделение и инициализацию массива. Если вы как-то определили, что, например, 95% вызовов с 3 или менее аргументами и только 5% с 4 или более, то перегрузка выглядит следующим образом
public static <E> ImmutableList<E> of();
public static <E> ImmutableList<E> of( E e );
public static <E> ImmutableList<E> of( E e1, E e2 );
public static <E> ImmutableList<E> of( E e1, E e2, E e3 );
public static <E> ImmutableList<E> of( E e1, E e2, E e3, E... es );
приводит к хорошему приросту производительности в 95% случаев. Иными словами, средняя производительность случая повышается.
В дополнение к другим отличным ответам, здесь есть небольшое преимущество в производительности во время выполнения (помимо избежания выделения массива), заключающееся в том, что перегрузки с нулевым аргументом и одно аргументом возвращают реализации, оптимизированные для представления пустых списков и списков с единичными экземплярами. (соответственно).
Если бы мы не имели отдельных перегрузок методов для них и включали только один метод на основе varargs, то этот метод мог бы выглядеть примерно так:
public static <E> ImmutableList<E> of(E... es) {
switch (es.length) {
case 0:
return emptyImmutableList();
case 1:
return singletonImmutableList(es[0]);
default:
return defaultImmutableList(es);
}
}
Производительность случая переключения (или проверок if-else) не будет плохой для большинства вызовов, но все равно не нужна, поскольку может иметь перегрузки методов для каждой оптимизации, и компилятор всегда знает, какую перегрузку вызывать. На клиентский код не ложится бремя, так что выиграть легко.