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

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

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

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

Стоит ли путать прирост производительности? Если да, то в идеале это создаст отдельный метод для любого N элементы?

3 ответа

Решение

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

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

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

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

Если вы разрабатываете 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) { ... }
}

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

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) { ... }
}

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

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

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

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

Так что у тебя есть:

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

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

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