我最近遇到了 java
@SafeVarargs
注释。谷歌搜索 Java 中的可变参数函数不安全的原因让我相当困惑(堆中毒?擦除类型?),所以我想知道一些事情:
从
@SafeVarargs
的意义上来说,是什么使得可变参数Java函数不安全(最好以深入示例的形式进行解释)?为什么这个注释要留给程序员自行决定?这不是编译器应该能够检查的东西吗?
是否有一些必须遵守的标准才能确保他的功能确实是 varags 安全的?如果没有,确保它的最佳实践是什么?
1)互联网和 StackOverflow 上有很多关于泛型和可变参数的特定问题的示例。基本上,当你有可变数量的类型参数类型的参数时:
<T> void foo(T... args);
在Java中,可变参数是一种语法糖,在编译时会进行简单的“重写”:
X...
类型的可变参数被转换为X[]
类型的参数;每次调用此 varargs 方法时,编译器都会收集 varargs 参数中的所有“变量参数”,并创建一个数组,就像 new X[] { ...(arguments go here)... }
一样。
当可变参数类型像
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");
}
}
这里的问题是,我们依赖
args
的类型为 T[]
才能将其返回为 T[]
。但实际上运行时参数的类型不是 T[]
的实例。
3) 如果你的方法有一个类型为
T...
的参数(其中 T 是任何类型参数),那么:
T
T[]
依赖于数组运行时类型的事情包括:将其作为类型
T[]
返回、将其作为实参传递给 T[]
类型的参数、使用 .getClass()
获取数组类型、将其传递给依赖的方法关于数组的运行时类型,如 List.toArray()
和 Arrays.copyOf()
等
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()
并确保我无法引入堆污染,这就是我所做的。
我知道这只能回答你的第三个问题。 newacct 已经为上面的 #1 和 #2 给出了很好的答案,但我没有足够的声誉来将此作为评论。 :P
@SafeVarargs 用于表示方法不会造成堆污染。
堆污染是指我们在泛型数组中混合不同的参数化类型。
例如:
public static <T> T[] unsafe(T... elements) {
return elements;
}
Object [] listOfItems = unsafe("some value", 34, new ArrayList<>());
String stringValue = (String) listOfItems[0]; // some value
String intValue = (String) listOfItems[1]; // ClassCastException
如你所见,如果我们不猜测类型,这样的实现很容易导致
ClassCastException
。