在整个Google Guava库中,我注意到了使用"一个(或两个)加上var gugs"技术
示例:
void add(T value, T... moreValueArr)
void add(T value, T value2, T... moreValueArr)
我花了一些时间才弄清楚原因:为了防止使用零args(第一种情况下)或一种arg(第二种情况)调用。
如果在下面的情景A和B之间做出选择,那么进一步扩展这项技术,哪个更好?我希望有深入Java知识的人可以提供见解。
情景A :(两种方法)
void add(T... valueArr)
void add(Iterable<? extends T> iterable)
情景B :(三种方法)
void add(T value, T... moreValueArr)
void add(T[] valueArr)
void add(Iterable<? extends T> iterable)
为什么 B 可能更好的一个想法:我注意到许多Java程序员都不知道数组可以直接作为var args传递。因此, B 可能提供可能的提示。
最后,我意识到 B 有额外的开发,测试和维护开销。请把这些考虑放在一边。
这个问题是我原来问题的一个微妙变化:Java varags method param list vs. array
答案 0 :(得分:6)
结论将在最后绘制。如果您只想得出结论,请跳到最后。
主要目标是表现。
如果有很多用例只有1或2个元素可以通过,则可以避免创建数组。是的,仍然会传递一个零长度数组,但由于无法修改零长度数组,因此允许JVM传递共享实例,例如,如果缓存完全没有性能影响。
最明亮的例子是EnumSet.of()
方法(返回列出的枚举实例的EnumSet
)。
您将看到以下重载:
static <E extends Enum<E>> EnumSet<E> of(E e);
static <E extends Enum<E>> EnumSet<E> of(E first, E... rest);
static <E extends Enum<E>> EnumSet<E> of(E e1, E e2);
static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3);
static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4);
static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5);
如果使用5个或更少的元素调用of()
方法,则不会创建任何数组,因为存在可能需要5个或更少元素的重载。性能也在EnumSet.of(E first, E... rest)
此工厂的参数列表使用varargs功能,可能是 用于创建最初包含任意数量的枚举集 元素,但它可能比过载运行慢 不要使用varargs。
至于为什么要使用of(E first, E... rest)
,即使有单独的of(E e1, E e2)
:
这只是实现的便利。如果声明first
参数,则可以使用它而无需检查数组的长度或必须使用索引。例如,您可以使用它来检查其类型(这在使用泛型时通常很重要。)
它实际上并不强制传递至少一个参数,因为如果只有vararg参数,你可以像传递空数组一样轻松传递null
。
Vararg vs array
如果数组的类型不是基本类型,则没有真正的区别,除了数组参数强制您显式创建数组,而vararg参数允许您传递数组或只列出元素。 / p>
尽管数组参数有历史原因(varargs只加入Java 5.0),但仍然需要使用数组:
可重复参数
在这种情况下, Iterable
是将多个值传递给方法的替代方法,但它不是数组和vararg参数的替代。 Iterable
用于集合,因为如果参数为Iterable
(数组未实现Iterable
),则无法使用数组或列出元素。如果调用者具有集合形式的输入数据(例如List
或Set
),Iterable
参数是最方便,最通用和最有效的方法元素。
得出结论
回到原来的场景A和B.由于两个场景都包含带Iterable
参数的方法,并且Iterable
s不与数组“混合”,我通过省略来减少问题那些:
情景A :(一种方法)
void add(T... valueArr)
情景B :(两种方法)
void add(T value, T... moreValueArr)
void add(T[] valueArr)
由于方法add()
只从数组中读取(而不是写入数据;假设来自名称add
),因此每个用例都可以用它们来解决。为简单起见,我会选择Scenario A 。
答案 1 :(得分:3)
您提供的选项A和B没有特别的理由是相互排斥的,也不是唯一可用的选项。相反,要认识到varargs 解决了与集合不同的问题。
Guava(和现代Java最佳实践)强烈建议使用集合而不是数组。它们比数组更具可扩展性和可互换性,并提供更强大的抽象,例如延迟迭代。
另一方面,varargs为调用方法提供了一种很好的方法。如果您希望人们想要直接传递参数,而不是作为集合的一部分,那么varargs会更方便。几乎所有的varargs样式方法都应该简单地调用你的集合等效方法,然后尽可能少地做,因为只要你必须真正使用数组,这种便利就会变得很不方便。创建直接将数组作为参数的新方法没有什么价值。
但为什么奇怪的vararg签名?
您想要或需要签名类型的一些原因,您会看到Guava使用很多:
执行更严格的签名:
您可以使用任意数量的参数调用vararg方法,包括零。如果您的方法在没有任何参数的情况下没有任何意义,void add(T one, T ... rest)
的签名会在类型级别强制执行该要求。
为避免泛色冲突:
有时您会想要定义一个varargs方法,由于type erasure,该方法与另一种方法相同。例如,如果您使用足够通用的类型定义void add(T ... arr)
以及void add(Iterable<T> iter)
,那么传递iterable可能会实际匹配varargs方法。使用void add(T one, T ... arr)
有助于使编译器保持这些方法的不同。 (我将尝试找到这个问题的更具体的例子。)
避免创建对象:
有时您会看到方法签名似乎可能是varargs,但不是,例如ImmutableList.of()
的重载。这些方法的目的是避免在varargs调用中必须在幕后发生的额外数组分配。这实际上更像是泄漏抽象的情况,而且在功能上可以安全地忽略。除非您正在实施一种可以像Guava实用程序那样经常调用的方法,否则分配节省可能不值得增加代码复杂性。
总之,坚持使用Iterable
,Iterator
或适当的Collection
子接口编写方法。如果您预计您的方法的用户想要传递单个值,请考虑添加将内部参数包装到集合中的varargs方法,以方便调用者。
答案 2 :(得分:2)
我会稍微修改一下b:
void add (T first, T ... more){
// call the second version
add(Lists.asList(first, more);
}
void add(Iterable<? extends T> data){
... // do stuff here
}
您不应该提供数组方法。阵列已经过时,维护噩梦。如果您的库的客户端实际上有一个数组,他们仍然可以将它传递给包含在Arrays.asList(...)
如果您更像是一个控制狂,您可以在第一种方法中进行额外的检查:
void add (T first, T ... more){
// call the second version
add(more.length == 0
? Collections.singleton(first)
: Lists.asList(first, more));
}
虽然我怀疑它会比使用Lists.asList
更有效率答案 3 :(得分:0)
我会选择A,并鼓励用户(在方法文档中)将数组传递给您的方法而不是varargs,因为每次调用varargs方法时都必须创建anonnymous数组,如here所述。
您还可以使用wildcard改进add(Iterable<T> data)
方法,并将其更改为add(Iterable<? extends T> data)
。
编辑:(回复@ Torben的评论)
我们假设您拨打add(a,b,c)
lot ,如果每次都创建匿名数组,如上面link所述,那么你将浪费时间在heap上分配内存以及让垃圾收集器忙碌。
另一方面,我不确定正在做多少优化,所以它不可能那么可怕。但是,您可以通过创建自己的1数组并继续使用它来确保不会创建任何数组:
YourType[] arr3 = new YourType[3]; //declared as field
...
arr3[0] = a;
arr3[1] = b;
arr3[2] = c;
add(arr3);
但这不是线程安全的,所以在多线程环境中,确保每个线程使用不同的数组。
答案 4 :(得分:0)
明显的区别在于,如果你定义一个方法
void a(int[] args) { }
void b(int ... args) { }
你不能打电话
a(1, 2, 3);
但你可以打电话
b(new int[] { 1, 2, 3 });
如果你想在数组之后有参数,那么实际上只需要数组类型的参数。
a(int[] args, int arg);
a(int ... args, int arg); // Won't even compile.
另一个区别是,如果你的方法需要至少一个参数,那么你必须进行参数长度检查,除非你像这样定义你的方法
c(int arg, int ... args) { }
你在代码中做了一个折衷,因为你必须以不同的方式处理第一个参数,但IMO更严格的API定义是值得的。