我最近遇到了 java@SafeVarargs
注释。谷歌搜索是什么让 Java 中的可变参数函数不安全让我很困惑(堆中毒?擦除类型?),所以我想知道一些事情:
是什么让可变参数 Java 函数在
@SafeVarargs
某种意义上不安全(最好以深入示例的形式进行解释)?为什么这个注释留给程序员自由裁量权?这不是编译器应该能够检查的吗?
是否必须遵守一些标准以确保他的功能确实是 varags 安全的?如果没有,确保它的最佳做法是什么?
我最近遇到了 java@SafeVarargs
注释。谷歌搜索是什么让 Java 中的可变参数函数不安全让我很困惑(堆中毒?擦除类型?),所以我想知道一些事情:
是什么让可变参数 Java 函数在@SafeVarargs
某种意义上不安全(最好以深入示例的形式进行解释)?
为什么这个注释留给程序员自由裁量权?这不是编译器应该能够检查的吗?
是否必须遵守一些标准以确保他的功能确实是 varags 安全的?如果没有,确保它的最佳做法是什么?
1) Internet 和 StackOverflow 上有很多关于泛型和可变参数的特定问题的示例。基本上,它是当您拥有可变数量的类型参数类型的参数时:
<T> void foo(T... args);
在 Java 中,可变参数是一种语法糖,它在编译时经过简单的“重写”:类型的可变参数X...
转换为类型的参数X[]
;每次调用这个 varargs 方法时,编译器都会收集 varargs 参数中的所有“变量参数”,并创建一个数组,就像new X[] { ...(arguments go here)... }
.
当 varargs 类型具体如String...
. 当它是一个类型变量T...
时,它也可以在T
已知是该调用的具体类型时工作。例如,如果上面的方法是一个类的一部分Foo<T>
,并且你有一个Foo<String>
引用,那么调用foo
它就可以了,因为我们知道代码T
中String
的那个点。
T
但是,当 的“值”是另一个类型参数时,它不起作用。在 Java 中,不可能创建类型参数组件类型 ( new T[] { ... }
) 的数组。所以 Java 改为使用new Object[] { ... }
(这里Object
是 的上限T
;如果有不同的上限,那就是它而不是Object
),然后给你一个编译器警告。
那么创建new Object[]
而不是new T[]
什么有什么问题呢?好吧,Java 中的数组在运行时就知道它们的组件类型。因此,传递的数组对象在运行时将具有错误的组件类型。
对于可变参数的最常见用法,简单地迭代元素,这没有问题(你不关心数组的运行时类型),所以这是安全的:
@SafeVarargs
final <T> void foo(T... args) {
for (T x : args) {
// do stuff with x
}
}
但是,对于任何依赖于传递数组的运行时组件类型的东西,它都是不安全的。这是一个不安全和崩溃的简单示例:
class UnSafeVarargs
{
static <T> T[] asArray(T... args) {
return args;
}
static <T> T[] arrayOfTwo(T a, T b) {
return asArray(a, b);
}
public static void main(String[] args) {
String[] bar = arrayOfTwo("hi", "mom");
}
}
这里的问题是我们依赖于 to 的类型args
才能T[]
将其返回为T[]
. 但实际上运行时参数的类型不是T[]
.
3) 如果您的方法具有T...
类型参数(其中 T 是任何类型参数),则:
T
T[]
依赖于数组的运行时类型的事情包括:将其作为 type 返回T[]
,将其作为参数传递给 type 的参数,T[]
使用获取数组类型.getClass()
,将其传递给依赖于数组的运行时类型的方法,例如List.toArray()
和Arrays.copyOf()
, ETC。
2)我上面提到的区分太复杂了,不容易自动区分。
对于最佳实践,请考虑这一点。
如果你有这个:
public <T> void doSomething(A a, B b, T... manyTs) {
// Your code here
}
将其更改为:
public <T> void doSomething(A a, B b, T... manyTs) {
doSomething(a, b, Arrays.asList(manyTs));
}
private <T> void doSomething(A a, B b, List<T> manyTs) {
// Your code here
}
我发现我通常只添加可变参数以方便我的调用者。对于我的内部实现来说,使用List<>
. 因此,要背负Arrays.asList()
并确保我无法引入堆污染,这就是我所做的。
我知道这只会回答你的#3。newacct 对上面的#1 和#2 给出了很好的答案,我没有足够的声誉将其作为评论留下。:P