Java 9、Set.of() 和 Map.of() 可变参数重载

Java 9, Set.of() and Map.of() varargs overloads

我正在研究 Immutable 集合的工厂方法。 我看到 Set.of() 方法有 10 个可变参数重载(Map.of() 也一样)。实在想不明白为什么会有这么多。最后函数 ImmutableCollections.SetN<>(elements) 还是被调用了。

在文档中我发现了这个:

While this introduces some clutter in the API, it avoids array allocation, initialization, and garbage collection overhead that is incurred by varargs calls.

混乱确实值得提高性能吗?如果是,理想情况下是否会为任何 N 元素创建一个单独的方法?

目前无论如何都会调用该方法 - 这可能会改变。例如,它可能会创建一个只有三个元素的 Set,4 等等。

另外 并非所有这些 委托给 SetN - 具有零、一和两个元素的实际 类 的 ImmutableCollections.Set0 ]、ImmutableCollections.Set1ImmutableCollections.Set2

或者你可以阅读关于这个问题的实际问题...阅读那个问题中Stuart Marks的评论——因为他就是那个人创建这些集合。

我想这取决于您正在使用的 API 的范围。在谈论那些不可变的 类 时,您是在谈论作为 jdk 的一部分包含的东西;所以范围很广。

所以你有:

  1. 一方面,这些不可变 类 可能会被每一位都很重要的应用程序使用(并且在 allocation/deallocation 中浪费了每一纳秒)。
  2. 另一方面,没有这些需求的应用程序不会受到负面影响
  3. 唯一的 'negative' 方面是 API 的实现者需要处理更多的混乱,因此它会影响可维护性(但在这种情况下不是什么大事)。

如果你正在实现你自己的东西,我不会太在意(但要小心可变参数参数)除非你真的需要担心那些额外的位(和额外的性能等)。

这的某些方面可能是未来证明的一种形式。

如果你进化一个API,你需要注意方法签名会如何变化,所以如果我们有

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

我们可以说可变参数已经足够好了...除了可变参数 强制 对象数组的分配,虽然相当便宜 - 但实际上会影响性能.参见示例 this microbenchmark,当切换到可变参数形式时,无操作日志记录的吞吐量损失了 50%(即日志级别低于可记录级别)。

好的,所以我们做了一些分析,说最常见的情况是单例,所以我们决定重构...

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]) 是模棱两可的……如果我们没有在开始时添加可变参数就好了……

所以我认为他们所做的是添加足够的显式参数,以达到额外堆栈大小满足可变参数创建成本的程度,这可能是大约 10 个参数(至少基于 Log4J2 在添加时所做的测量他们对版本 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,每个人都赢了......不需要重新编译