Почему 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) не будет плохой для большинства вызовов, но все равно не нужна, поскольку может иметь перегрузки методов для каждой оптимизации, и компилятор всегда знает, какую перегрузку вызывать. На клиентский код не ложится бремя, так что выиграть легко.

Другие вопросы по тегам