Java SafeVarargs注释,是否存在标准或最佳实践?

我最近遇到了java @SafeVarargs注释。在谷歌上搜索为什么Java中的可变函数不安全,这让我相当困惑(堆中毒?擦除类型?),所以我想知道一些事情:

  1. 是什么使得可变变量Java函数在@SafeVarargs意义上不安全(最好以深入示例的形式解释)?

  2. 为什么这个注释由程序员自行决定?这难道不是编译器应该能够检查的东西吗?

  3. 有没有什么标准是必须遵守的,以确保他的功能确实是安全的?如果不是,什么是确保它的最佳实践?

31535 次浏览

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是可以的,因为我们知道在代码的这一点上TString

然而,当T的“value”是另一个类型参数时,它就不起作用了。在Java中,不可能创建类型参数组件类型(new T[] { ... })的数组。所以Java改为使用new Object[] { ... }(这里ObjectT的上限;如果上界不同,则会是Object),然后给你一个编译器警告。

那么创建new Object[]而不是new T[]或其他什么有什么问题呢?Java中的数组在运行时知道它们的组件类型。因此,传递的数组对象将在运行时具有错误的组件类型。

对于可能是varargs最常见的用法,简单地迭代元素,这是没有问题的(你不关心数组的运行时类型),所以这是安全的:

@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");
}
}

这里的问题是,为了返回T[],我们依赖args的类型为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对上面的第一条和第二条给出了一个很好的答案,我没有足够的声誉来留下这条评论。: P

@SafeVarargs用于表示方法不会造成堆污染。 堆污染是当我们在泛型数组中混合不同的参数化类型时

axample:

    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。