Какой смысл перегружать Удобство Factory Методы коллекций в Java 9
Java 9 поставляется с удобными factory методами для создания неизменяемых списков. Наконец, создание списка так же просто, как:
List<String> list = List.of("foo", "bar");
Но есть 12 перегруженных версий этого метода, 11 с 0 до 10 элементами и один с var args.
static <E> List<E> of(E... elements)
То же самое происходит с Set
и Map
.
Поскольку существует метод var args, в чем смысл иметь дополнительные 11 методов?
Я думаю, что var-args создает массив, поэтому другие 11 методов могут пропустить создание дополнительного объекта, и в большинстве случаев будут выполняться 0 - 10 элементов. Есть ли другая причина для этого?
Ответы
Ответ 1
Из самого документа JEP -
Описание -
Они будут включать перегрузки varargs, так что нет фиксированного предела по размеру коллекции. Однако созданные экземпляры коллекции могут быть настроены для меньших размеров. Специальные API-интерфейсы (фиксированный аргумент перегрузки) для до десяти элементов. Пока это вводит некоторый беспорядок в API, он избегает выделения массива, инициализации и накладных расходов на сбор мусора, которые понесены varargs calls.. Значительно, исходный код сайта вызова одинаковый независимо от того, вызвана ли перегрузка с фиксированным-arg или varargs.
Изменить - добавить мотивацию и как уже упоминалось в комментариях @CKing тоже:
Non-Goals -
Не стоит поддерживать высокопроизводительные масштабируемые коллекции с произвольным числом элементов. Основное внимание уделяется небольшим коллекциям.
Мотивация -
Создание небольшой немодифицируемой коллекции (скажем, набора) предполагает ее создание, сохранение ее в локальной переменной и несколько раз добавление() к ней, а затем ее завершение.
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));
Java 8 Stream API может использоваться для создания небольших коллекций путем объединения методов и коллекционеров потока factory.
// Java 8
Set<String> set1 = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(Collectors.toSet()));
Значительную часть литературных сборников можно получить, предоставив библиотечные API для создания небольших экземпляров коллекции, значительно снижая стоимость и риск по сравнению с изменением языка. Например, код для создания экземпляра Small Set может выглядеть так:
// Java 9
Set set2 = Set.of("a", "b", "c");
Ответ 2
Как вы подозревали, это повышение производительности. Методы Vararg создают массив "под капотом", а метод, принимающий 1-10 аргументов, позволяет избежать этого избыточного создания массива.
Ответ 3
Вы можете найти следующий отрывок из статьи 42 Джоша Блоха "Эффективная Ява" (2-е изд.):
Каждый вызов метода varargs вызывает распределение и инициализацию массива. Если вы определили эмпирически, что не можете позволить себе эту стоимость, но вам нужна гибкость varargs, есть образец, который позволяет вам иметь ваш торт и есть его тоже. Предположим, вы определили, что 95 процентов вызовов метода имеют три или меньше параметров. Затем объявите пять перегрузок метода, по одному с нулем на три обычных параметра, и один метод varargs для использования, когда количество аргументов превышает три [...]
Ответ 4
Вы также можете посмотреть на это наоборот. Поскольку методы varargs могут принимать массивы, такой метод будет служить альтернативным способом преобразования массива в List
.
String []strArr = new String[]{"1","2"};
List<String> list = List.of(strArr);
Альтернативой этому подходу является использование Arrays.asList
, но любые изменения, сделанные в List
в этом случае, отразились бы в массиве, который не соответствует List.of
. Поэтому вы можете использовать List.of
, если вы не хотите, чтобы List
и массив были синхронизированы.
Примечание Обоснование, данное в спецификации, похоже на микрооптимизацию для меня. (Это подтверждено владельцем самого API в комментариях к другому ответу)
Ответ 5
По Java doc: коллекции, возвращаемые удобными методами factory, более эффективны по площади, чем их изменяемые эквиваленты.
До Java 9:
Set<String> set = new HashSet<>(3); // 3 buckets
set.add("Hello");
set.add("World");
set = Collections.unmodifiableSet(set);
В приведенной выше реализации Set
создается 6 объектов: немодифицируемая оболочка; HashSet
, который содержит a HashMap
; таблица ведер (массив); и два экземпляра Node (по одному для каждого элемента). Если VM занимает 12 байтов на объект, тогда 72 байта потребляют как служебные, плюс 28 * 2 = 56 байтов для 2 элементов. Здесь большая сумма потребляется накладными расходами по сравнению с данными, хранящимися в коллекции. Но в Java 9 эти накладные расходы очень малы.
После Java 9:
Set<String> set = Set.of("Hello", "World");
В вышеприведенной реализации Set
создается только один объект, и это займет очень меньше места для хранения данных из-за минимальных издержек.