195

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

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

  2. 为什么这个注释留给程序员自由裁量权?这不是编译器应该能够检查的吗?

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

4

2 回答 2

271

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它就可以了,因为我们知道代码TString的那个点。

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)我上面提到的区分太复杂了,不容易自动区分。

于 2013-01-10T06:43:43.480 回答
6

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

如果你有这个:

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

于 2018-07-11T09:48:13.593 回答