Подтвердить что ты не робот

Перегрузки Java 9, Set.of() и Map.of() varargs

Итак, я изучаю методы factory для коллекций Immutable. Я вижу, что метод Set.of() имеет 10 перегрузок varargs (тот же для Map.of()). Я действительно не понимаю, почему их так много. В конце концов функция ImmutableCollections.SetN<>(elements) вызывается в любом случае.

В документации я нашел это:

В то время как это вводит некоторый беспорядок в API, оно позволяет избежать накладных расходов на распределение массивов, инициализацию и сборку мусора, которые возникают в результате вызовов varargs.

Неужели беспорядок действительно стоит увеличения производительности? Если да, идеально ли было бы создать отдельный метод для любых элементов N?

4b9b3361

Ответ 1

В данный момент этот метод вызывается в любом случае - это может измениться. Например, может быть, что он создает Set только с тремя элементами, 4 и так далее.

Также не все из них делегируются SetN - те, у которых есть нуль, один и два элемента имеют действительные классы ImmutableCollections.Set0, ImmutableCollections.Set1 и ImmutableCollections.Set2

Или вы можете прочитать фактический вопрос об этом... здесь Прочитайте комментарии от Stuart Marks в этом вопросе, поскольку он - человек, который создал эти Коллекции,

Ответ 2

Некоторые аспекты этого могут быть формой будущей проверки.

Если вы разрабатываете API, вам нужно обратить внимание на то, как будет изменяться сигнатура метода, поэтому, если у нас есть

public class API {
  public static final <T> Set<T> of(T... elements) { ... }
}

Можно сказать, что varargs достаточно хорош... за исключением того, что varargs заставляет выделять массив объектов, который - хотя и разумно дешевый - действительно влияет на производительность. См., Например, этот микрообъект, который показывает 50% -ную потерю пропускной способности для ведения журнала без операции (то есть уровень журнала ниже, чем регистрируемый) при переключении к форме varargs.

Хорошо, поэтому мы делаем некоторый анализ и говорим, что наиболее распространенным случаем является синглтон, поэтому мы решаем рефакторинг...

public class API {
  public static final <T> Set<T> of(T first) { ... }
  public static final <T> Set<T> of(T first, T... others) { ... }
}

Ooops... не бинарный совместимый... он совместим с исходным кодом, но не совместим с бинарными... чтобы сохранить двоичную совместимость, нам нужно сохранить предыдущую подпись, например.

public class API {
  public static final <T> Set<T> of(T first) { ... }
  @Deprecated public static final <T> Set<T> of(T... elements) { ... }
  public static final <T> Set<T> of(T first, T... others) { ... }
}

Ugh... Код IDE завершен, теперь беспорядок... плюс, как создать набор массивов? (вероятно, более актуальным, если я использовал список) API.of(new Object[0]) неоднозначно... если бы мы не добавили vararg в начале...

Итак, я думаю, что они сделали, добавив достаточно явных аргументов, чтобы достичь точки, где дополнительный размер стека соответствует стоимости создания vararg, что, вероятно, составляет около 10 аргументов (по крайней мере, на основе измерений, которые Log4J2 сделал при добавлении их varargs к API версии 2)... но вы делаете это для доказательства, основанного на будущем...

Другими словами, мы можем обманывать все случаи, когда у нас нет доказательств, требующих специализированной реализации, и просто переходим к варианту vararg:

public class API {
  private static final <T> Set<T> internalOf(T... elements) { ... }
  public static final <T> Set<T> of(T first) { return internalOf(first); }
  public static final <T> Set<T> of(T first, T second) { return internalOf(first, second); }
  ...
  public static final <T> Set<T> of(T t1, T t2, T t3, T t4, T t5, T... rest) { ... }
}

Затем мы можем проанализировать и проанализировать шаблоны использования в реальном мире, и если мы увидим значительное использование до 4-мерной формы и контрольных показателей, показывающих, что это разумное первичное усиление, то в этот момент за кулисами мы меняем метод impl и каждый получает выигрыш... не требуется перекомпиляция

Ответ 3

Я думаю, это зависит от объема API, с которым вы работаете. Когда речь идет о тех Неизменяемых классах, вы говорите о вещах, включенных как часть jdk; поэтому масштаб очень широк.

Итак, у вас есть:

  • с одной стороны, что эти Неизменяемые классы могут использоваться приложениями, где каждый бит подсчитывается (и каждая наносекунда теряется в распределении/освобождении).
  • в другой стороне, что приложения без этих потребностей не оказывают негативного влияния.
  • единственная "отрицательная" сторона для разработчиков этого API, которая будет иметь больше беспорядка, чтобы иметь дело, поэтому она влияет на maintanability (но не большая вещь в этом случае).

Если бы вы реализовали свои собственные вещи, мне было бы безразлично об этом (но будьте осторожны с аргументами varargs), если вам действительно не нужно беспокоиться об этих дополнительных битах (и дополнительной производительности и т.д.).