В чем смысл перегруженных методов фабрики удобства для коллекций в Java 9
Java 9 поставляется с удобными фабричными методами для создания неизменяемых списков. Наконец, создание списка так же просто, как:
List<String> list = List.of("foo", "bar");
Но существует 12 перегруженных версий этого метода, 11 с 0 - 10 элементами и одна с переменными аргументами.
static <E> List<E> of(E... elements)
То же самое в случае с Set
а также Map
,
Поскольку существует метод var args, какой смысл иметь дополнительные 11 методов?
Я думаю, что var-args создают массив, поэтому остальные 11 методов могут пропустить создание дополнительного объекта, и в большинстве случаев это будет делать от 0 до 10 элементов. Есть ли другая причина для этого?
6 ответов
Из самой документации JEP -
Описание -
Они будут включать в себя перегрузки varargs, поэтому не существует фиксированного ограничения на размер коллекции. Однако созданные таким образом экземпляры коллекции могут быть настроены на меньшие размеры. Будут предоставлены специальные API-интерфейсы (перегрузки с фиксированным аргументом) для десяти элементов. Хотя это вносит некоторую путаницу в API, оно позволяет избежать выделения массивов, инициализации и издержек на сборку мусора, возникающих при вызовах varargs. Важно отметить, что исходный код сайта вызова остается неизменным независимо от того, вызывается ли перегрузка с фиксированным аргументом или varargs.
Редактировать - чтобы добавить мотивацию и как уже упоминалось в комментариях @CKing тоже:
Не Цели -
Не стоит поддерживать высокопроизводительные, масштабируемые коллекции с произвольным количеством элементов. Основное внимание уделяется небольшим коллекциям.
Мотивация -
Создание небольшой неизменяемой коллекции (скажем, набора) включает ее создание, сохранение в локальной переменной, несколько раз вызывая для нее add(), а затем оборачивая ее.
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));
Java 8 Stream API можно использовать для создания небольших коллекций путем объединения методов и сборщиков потоковой фабрики.
// Java 8
Set<String> set1 = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(Collectors.toSet()));
Большую выгоду от литералов коллекций можно получить, предоставляя библиотечные API для создания небольших экземпляров коллекций при значительно меньших затратах и рисках по сравнению со сменой языка. Например, код для создания небольшого экземпляра Set может выглядеть так:
// Java 9
Set set2 = Set.of("a", "b", "c");
Как вы и подозревали, это повышение производительности. Методы Vararg создают массив "под капотом", а наличие метода, который принимает 1-10 аргументов напрямую, позволяет избежать создания этого избыточного массива.
Вы можете найти следующий отрывок из пункта 42 " Эффективной Явы" Джоша Блоха (2-е изд.):
Каждый вызов метода varargs вызывает выделение и инициализацию массива. Если вы эмпирически определили, что не можете позволить себе такую стоимость, но вам нужна гибкость вараггов, существует модель, которая позволяет вам съесть свой торт и съесть его тоже. Предположим, вы определили, что 95 процентов вызовов метода имеют три или менее параметров. Затем объявите пять перегрузок метода, по одному с нулем через три обычных параметра, и один метод varargs для использования, когда число аргументов превышает три [...]
Вы также можете посмотреть на это с другой стороны. Так как методы varargs могут принимать массивы, такой метод будет служить альтернативным средством преобразования массива в List
,
String []strArr = new String[]{"1","2"};
List<String> list = List.of(strArr);
Альтернативой этому подходу является использование Arrays.asList
но любые изменения, внесенные в List
в этом случае будет отражаться в массиве, который не имеет место с List.of
, Поэтому вы можете использовать List.of
когда ты не хочешь List
и массив для синхронизации.
Примечание. Обоснование, данное в спецификации, кажется мне микрооптимизацией. (Это было подтверждено самим владельцем API в комментариях к другому ответу)
Этот шаблон используется для оптимизации методов, которые принимают параметры varargs.
Если вы можете выяснить, что в большинстве случаев вы используете только пару из них, вам, вероятно, следует определить перегрузки метода с количеством наиболее используемых параметров:
public void foo(int num1);
public void foo(int num1, int num2);
public void foo(int num1, int num2, int num3);
public void foo(int... nums);
Это поможет вам избежать создания массива при вызове метода varargs. Шаблон, используемый для оптимизации производительности:
List<String> list = List.of("foo", "bar");
// Delegates call here
static <E> List<E> of(E e1, E e2) {
return new ImmutableCollections.List2<>(e1, e2); // Constructor with 2 parameters, varargs avoided!
}
Более интересно то, что, начиная с 3-х параметров, мы снова делегируем конструктору varargs:
static <E> List<E> of(E e1, E e2, E e3) {
return new ImmutableCollections.ListN<>(e1, e2, e3); // varargs constructor
}
Пока это кажется странным, но, как я могу догадаться, это зарезервировано для будущих улучшений и, как вариант, потенциальная перегрузка всех конструкторов. List3(3 params), List7(7 params)...
и так далее.
Согласно документу Java: коллекции, возвращаемые методами фабрики удобства, занимают больше места, чем их изменяемые эквиваленты.
До Java 9:
Set<String> set = new HashSet<>(3); // 3 buckets
set.add("Hello");
set.add("World");
set = Collections.unmodifiableSet(set);
В приведенной выше реализации Set
Создается 6 объектов: неизменяемая оболочка; HashSet
, который содержит HashMap
; таблица ведер (массив); и два экземпляра узла (по одному для каждого элемента). Если виртуальная машина занимает 12 байтов на объект, то 72 байта потребляют как служебные данные, плюс 28*2 = 56 байтов для 2 элементов. Здесь большое количество потребляется накладными расходами по сравнению с данными, хранящимися в коллекции. Но в Java 9 эти издержки очень меньше.
После Java 9:
Set<String> set = Set.of("Hello", "World");
В приведенной выше реализации Set
только один объект создается, и это займет очень меньше места для хранения данных из-за минимальных издержек.