当设计具有可变参数长度的(高性能)方法时,例如
<T> void consume(T t1)
<T> void consume(T t1, T t2)
<T> void consume(T t1, T t2, T t3)
和 a) 最后带有 vararg 的另一个重载,例如ImmutableList.of()
<T> void consume(T t1, T t2, T t3, T... others)
或 b) 另一个带有单个 vararg 参数的重载,例如List.of()
<T> void consume(T... tees)
每个 vararg 变体的优点和缺点是什么?
void consume(T... tees)
的问题在于它都是语法糖。这个:
void consume(T... tees) {
....
}
consume(a, b);
这是编译为:
void consume(T... tees) {
....
}
consume(new Object[] {a, b});
“成本”是创建该 2 元素数组。这并不是一个很高的成本,而且一般来说“性能”根本不是你可以这样推理的:Hotspot 比你想象的要复杂得多远,所以,在你之前你真的需要一份分析器报告只是盲目地假设制作 2-args 数组实际上是一个性能问题。
在到达可变参数之前,拥有一堆参数数量不断增加的方法的目的是避免创建数组。然而,你所展现的风格有点错误。这是对的:
void consume(T a) { ??? }
void consume(T a, T b) { ??? }
void consume(T a, T b, T c, T... rest) { ??? }
换句话说,你不断地添加一个参数,一旦你觉得已经足够了,最后一个也就会得到
T... rest
。您不需要同时使用 consume(T a, T b, T c)
和 (T a, T b, T c, T... rest)
,因为这意味着 consume(x, y, z)
形式的调用是不明确的(您是用零“其余”参数调用第一个还是第二个,这是有效的java?)
那么什么时候你会“停止”并使用 varargs 呢?好吧,你告诉我:当尝试节省数组分配的意义不再值得做时; “如果你无论如何都要传递大量的参数,性能可能会超出预期”和“向此方法传递 12 个参数是极其罕见的,因此不值得尝试优化”的某种组合。
一般来说,你应该只编写一种方法(
consume(T... tees)
),而不用担心所有这些重载。 JVM“非常擅长”优化常见模式,而可变参数是一种非常常见的模式。重载维护起来很烦人,更难记录,而且还增加了 API 的烦恼 - 如果我碰巧有一个 T 元素数组,我现在不能再调用你的 API。