В чем смысл перегруженных методов фабрики удобностей для коллекций в Java 9

22

Java 9 поставляется с удобными фабричными методами для создания неизменяемых списков. Наконец, создание списка так же просто, как:

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 элементов. Есть ли другая причина для этого?

    
задан ares 29.01.2017 в 09:20
источник
  • Вы только что ответили на свой собственный вопрос - перегрузка его аргументами 0-10 пропускает ненужные создания массивов. –  luk2302 29.01.2017 в 09:24
  • @ Marco13 Я голосую за возобновление на основе идеи, что есть много вопросов, которые превосходят технологии. Также голосование возобновляется, поскольку, учитывая, что эти функции теперь доступны в java, люди будут искать «java 9 collection.of» больше, чем «Guava colciton.of». –  CKing 29.01.2017 в 16:45
  • @ Marco13 Я считаю, создавая канонический вопрос и указывая на эти два вопроса, поскольку дубликаты будут иметь смысл. Пока мы этого не сделаем, я чувствую, что этот вопрос должен оставаться открытым. Я бы не хотел болтать на пространстве комментариев этого вопроса для этого обсуждения, поэтому давайте посмотрим, что говорят другие. –  CKing 29.01.2017 в 17:30
  • Я голосую, чтобы вновь открыть. Обоснование подобных перегрузок в других API (Guava, EnumSet) было иным - они обеспечивали перегрузки, потому что в то время не существовало @SafeVarargs, тогда как драйвер для перегрузок в JEP 269 был производительности. –  Stefan Zobel 29.01.2017 в 18:28
  • @StefanZobel согласен; особенно потому, что общая точка обсуждения состоит в том, что это микро-оптимизация, чтобы исключить создание массива, и в то же время все методы (кроме двух параметров) делегируют этому: @SafeVarargs @SuppressWarnings («unchecked») SetN (E .. . input) { –  Eugene 30.01.2017 в 22:26
Показать остальные комментарии

6 ответов

25

Из самого документа JEP -

Описание -

  

Они будут включать перегрузки varargs, так что нет фиксированного предела   по размеру коллекции. Однако созданные экземпляры коллекции   могут быть настроены для меньших размеров. Специальные API-интерфейсы (фиксированный аргумент   перегрузки) для до десяти элементов. Пока это   вводит некоторый беспорядок в API, он избегает выделения массива,   инициализации и накладных расходов на сбор мусора, которые понесены   varargs calls. . Значительно, исходный код сайта вызова одинаковый независимо от того, вызвана ли перегрузка с фиксированным-arg или varargs.

Изменить - добавить мотивацию и, как уже упоминалось в комментариях @CKing:

Non-Goals -

  

Не стоит поддерживать высокопроизводительные масштабируемые коллекции   с произвольным числом элементов. Основное внимание уделяется небольшим коллекциям .

Мотивация -

Создание небольшой немодифицируемой коллекции (скажем, набора) предполагает ее создание, сохранение ее в локальной переменной и несколько раз добавление () к ней, а затем ее завершение.

Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));

API Java 8 Stream API можно использовать для создания небольших коллекций, объединяя потоковые фабричные методы и коллекторы.

// 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");
    
ответ дан nullpointer 29.01.2017 в 09:27
источник
  • Разве это не микро-оптимизация в эпоху обширного оборудования в вашем распоряжении на языке высокого уровня, например Java? Мне жаль, что для этого не было другой причины, но не может думать об одном –  CKing 29.01.2017 в 09:30
  • @CKing Да, это микро-оптимизация. Критерии производительности для библиотек JDK гораздо более строгие, чем для большинства приложений. Эти API-интерфейсы используются в самом JDK, и они влияют на время запуска, поэтому даже кажущиеся крошечными оптимизациями стоит того. –  Stuart Marks 30.01.2017 в 18:21
  • @StuartMarks, но затем все они (за исключением метода двух аргументов) делегируют этому: @SafeVarargs @SuppressWarnings ("unchecked") SetN (E ... input) { –  Eugene 30.01.2017 в 22:20
  • @Eugene Да, это похоронено в рамках реализации, и это может быть изменено совместимо. Он еще не полностью оптимизирован. –  Stuart Marks 30.01.2017 в 23:47
  • @CKing API-интерфейсы предназначены для общего использования. Но мы хотели иметь возможность использовать их в JDK, не жертвуя производительностью. Что касается общего использования новых API, обычно не требуется, чтобы исходный код знал или заботился о том, вызывает ли он метод fixed-arg или varargs. Если у вас есть Set.of (a1, a2, ... a10), и вы добавляете еще один аргумент arg, он заканчивает переход от фиксированного к вызову varargs, но в противном случае он ведет себя точно так же. Итак, просто используйте новые API. –  Stuart Marks 30.01.2017 в 23:53
Показать остальные комментарии
9

Как вы подозревали, это повышение производительности. Методы Vararg создают массив «под капотом», а метод, принимающий 1-10 аргументов, позволяет избежать этого избыточного создания массива.

    
ответ дан Mureinik 29.01.2017 в 09:25
источник
  • Разве это не микро-оптимизация в эпоху обширного оборудования в вашем распоряжении на языке высокого уровня, например Java? Мне жаль, что для этого не было другой причины, но я не могу думать об этом. –  CKing 29.01.2017 в 09:26
  • @CKing Разработчики языка / библиотеки должны учитывать производительность своего кода не только в коде, который выполняется нечасто; но также и то, что происходит, когда кто-то использует его в горячем цикле приложения с интенсивным вычислением. В тех случаях вещи, которые в противном случае могут быть отклонены как «бессмысленные микрооптимизации», имеют значительное влияние на производительность. –  Dan Neely 29.01.2017 в 16:35
  • @DanNeely Является ли этот аргумент действительным в этом случае? Насколько дорого стоит создать массив из 10 элементов? –  CKing 29.01.2017 в 16:38
  • @CKing Мои уровни интереса не поднимаются до точки написания теста, но книга, приведенная в ответе @JuBobs, подразумевает, что есть случаи, когда это может стать значительным. Мое предположение заключалось бы в том, что это воздействие было косвенным - от дополнительных сборок мусора, а не из-за прямого времени, затрачиваемого на перемещение данных через временный массив. –  Dan Neely 29.01.2017 в 17:44
  • @DanNeely & @ares: По иронии судьбы, сценарии с горячими циклами определенно считались несущественными для этой оптимизации. –  Stefan Zobel 29.01.2017 в 18:44
Показать остальные комментарии
6

Вы можете найти следующий фрагмент статьи 42 «Эффективная Java Джоша Блоха» (2-е изд.):

  

Каждый вызов метода varargs вызывает распределение и инициализацию массива. Если вы определили эмпирически, что вы не можете позволить себе эту стоимость, но вам нужна гибкость varargs, есть образец, который позволяет вам иметь ваш торт и есть его тоже. Предположим, вы определили, что 95 процентов вызовов метода имеют три или меньше параметров. Затем объявите пять перегрузок метода, по одному с нулем на три обычных параметра, и один метод varargs для использования, когда количество аргументов превышает три [...]

    
ответ дан Jubobs 29.01.2017 в 16:25
источник
  • Хорошо, поэтому реализация была скопирована прямо из текста Джоша Больча. –  ares 29.01.2017 в 16:42
  • Важнейшие два слова, которые следует принять к сведению: определяется эмпирически. Микро-оптимизации наиболее определенно требуют эмпирического доказательства. –  CKing 29.01.2017 в 16:47
3

Вы также можете посмотреть на это наоборот. Поскольку методы varargs могут принимать массивы, такой метод будет служить альтернативным способом преобразования массива в List .

String []strArr = new String[]{"1","2"};
List<String> list = List.of(strArr);

Альтернативой этому подходу является использование Arrays.asList , но любые изменения, внесенные в List в этом случае, будут отражаться в массиве, который не соответствует List.of . Поэтому вы можете использовать List.of , если вы не хотите, чтобы List и массив были в синхронизации.

Примечание Обоснование, данное в спецификации, похоже на микрооптимизацию для меня. (Это подтверждено владельцем самого API в комментариях к другому ответу)

    
ответ дан CKing 29.01.2017 в 09:32
источник
  • Это похоже на то, что вместо компилятора, создающего массив для меня, я сам создаю его. –  ares 29.01.2017 в 09:36
  • @ares Подумайте об этом так. Иногда вы говорите с кодом, который возвращает массив. Если вы хотите преобразовать этот массив в список, вы можете напрямую передать его методу List.of. Кроме того, список и массив не будут синхронизироваться по сравнению с методом Arrays.asList, так что это другой вариант использования этого метода. –  CKing 29.01.2017 в 10:02
1

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

    
ответ дан J-Alex 24.04.2018 в 09:56
источник
-1

Согласно Java doc : коллекции, возвращаемые методами фабрики удобство, более эффективны по площади, чем их изменяемые эквиваленты.

До Java 9: ​​

Set<String> set = new HashSet<>(3);   // 3 buckets

set.add("Hello");
set.add("World");
set = Collections.unmodifiableSet(set);

В приведенной выше реализации Set создается 6 объектов: немодифицируемая оболочка; HashSet , который содержит HashMap ; таблица ведер (массив); и два экземпляра узла (по одному для каждого элемента). Если VM занимает 12 байтов на объект, тогда 72 байта потребляют как служебные, плюс 28 * 2 = 56 байтов для 2 элементов. Здесь большая сумма потребляется накладными расходами по сравнению с данными, хранящимися в коллекции. Но в Java 9 эти накладные расходы очень малы.

После Java 9: ​​

Set<String> set = Set.of("Hello", "World");

В приведенной выше реализации Set создается только один объект, и это займет очень мало места для хранения данных из-за минимальных издержек.

    
ответ дан Mohit Tyagi 03.10.2017 в 09:41
источник
  • Хотя этот ответ может быть правильным, это не относится к заданному вопросу. –  ares 03.10.2017 в 09:44