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

Ore*_*ren 166 java variadic-functions java-7

我最近遇到了java @SafeVarargs注释.谷歌搜索使Java中的可变函数不安全的原因让我感到困惑(堆中毒?擦除类型?),所以我想知道一些事情:

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

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

  3. 是否有必须坚持一些标准,以确保他的功能确实安全?如果没有,确保它的最佳做法是什么?

new*_*cct 230

1)Internet和StackOverflow上有很多关于泛型和变量的特定问题的例子.基本上,当你有一个类型参数类型的可变数量的参数时:

<T> void foo(T... args);
Run Code Online (Sandbox Code Playgroud)

在Java中,varargs是一种语法糖,在编译时经历一个简单的"重写":类型的varargs参数X...被转换为类型的参数X[]; 每次调用这个varargs方法时,编译器都会收集varargs参数中的所有"变量参数",并创建一个类似的数组new X[] { ...(arguments go here)... }.

当varargs类型具体时,这很有效String....当它是类型变量时T...,它也适用于T已知为该调用的具体类型.例如,如果上面的方法是一个类的一部分Foo<T>,和你有一个Foo<String>参考,然后调用foo它会好起来的,因为我们知道TString在代码中的那个点.

但是,当"值" T是另一个类型参数时,它不起作用.在Java中,不可能创建类型参数组件类型(new T[] { ... })的数组.所以Java改为使用new Object[] { ... }(这里Object是上限T;如果上限是不同的,那就是代替Object),然后给你一个编译器警告.

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

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

@SafeVarargs
final <T> void foo(T... args) {
    for (T x : args) {
        // do stuff with x
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,对于任何依赖于传递的数组的运行时组件类型的东西,它将是不安全的.这是一个不安全和崩溃的简单示例:

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");
  }
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是,我们依靠的类型argsT[]为了回击T[].但实际上,运行时参数的类型不是实例T[].

3)如果你的方法有一个类型的参数T...(其中T是任何类型参数),那么:

  • 安全:如果您的方法仅取决于数组元素是实例的事​​实 T
  • 不安全:如果它取决于数组是实例的事​​实 T[]

依赖于数组的运行时类型的事情包括:将其作为类型返回T[],将其作为参数传递给类型的参数T[],使用获取数组类型.getClass(),将其传递给依赖于数组的运行时类型的方法,如List.toArray()Arrays.copyOf()

2)我上面提到的区别太复杂,不容易自动区分.

  • 可能值得注意的是,一个案例(大概)总是正确地使用`@SafeVarargs`是你对数组做的唯一事情是将它传递给另一个已经注释的方法(例如:我经常发现自己编写vararg方法,使用`Arrays.asList(...)`将它们的参数转换为列表并将其传递给另一个方法;这种情况总是可以用`@SafeVarargs`注释,因为`Arrays.asList`具有注解). (28认同)
  • 那么这一切都是有道理的.谢谢.所以只是为了看到我完全理解你,让我们说我们有一个类`FOO <T extends Something>`将一个varargs方法的T []作为一个`Somthing`s的数组返回是否安全? (7认同)
  • @Oren:它应该是 (4认同)
  • @newacct我不知道Java 7中是否存在这种情况,但Java 8不允许`@SafeVarargs`,除非该方法是`static`或`final`,否则它可能会在派生类中被覆盖不安全的事情. (3认同)
  • 在Java 9中,它也将被允许用于'private`方法,这些方法也无法被覆盖. (3认同)

小智 6

对于最佳实践,请考虑这一点。

如果你有这个:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}
Run Code Online (Sandbox Code Playgroud)

改成这样:

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
}
Run Code Online (Sandbox Code Playgroud)

我发现我通常只添加可变参数以方便我的调用者。对于我的内部实现来说,使用List<>. 因此,为了支持Arrays.asList()并确保我无法引入堆污染,这就是我所做的。

我知道这只能回答你的#3。newacct 对上面的 #1 和 #2 给出了很好的答案,我没有足够的声誉来把它作为评论。:P