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

2022-09-01 21:13:02

我正在研究收集的工厂方法。我看到该方法有10个varargs重载(与) 相同)。我真的不明白为什么会有这么多。最后,无论如何都会调用该函数。ImmutableSet.of()Map.of()ImmutableCollections.SetN<>(elements)

在文档中,我发现了这个:

虽然这会在 API 中引入一些混乱,但它避免了 varargs 调用产生的数组分配、初始化和垃圾回收开销。

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


答案 1

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

也不是所有元素都委托给 - 具有零,一和两个元素的那些具有的实际类,并且SetNImmutableCollections.Set0ImmutableCollections.Set1ImmutableCollections.Set2

或者您可以阅读有关此内容的实际问题...这里阅读该问题中的评论 -因为他是创建这些集合的人。Stuart Marks


答案 2

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

如果你发展了一个API,你需要注意方法签名将如何变化,所以如果我们有

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

我们可以说varargs已经足够好了...除了 varargs 强制分配对象数组,这虽然相当便宜,但实际上确实会影响性能。例如,请参阅此微基准标记,它显示切换到 varargs 形式时,无操作日志记录(即对数级别低于 loggable)的吞吐量损失 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代码完成现在是一团糟...以及如何创建一组数组?(如果我使用列表,可能更相关)是模棱两可的...如果我们一开始就没有添加vararg就好了...API.of(new Object[0])

所以我认为他们所做的是添加足够的显式参数,以达到额外的堆栈大小满足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,每个人都会赢......无需重新编译