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

2022-08-31 06:19:29

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

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

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

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


答案 1

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

<T> void foo(T... args);

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

当 varargs 类型是具体的,如 .当它是一个类型变量时,当已知是该调用的具体类型时,它也可以工作。例如,如果上面的方法是类的一部分,并且您有一个引用,那么调用它就可以了,因为我们知道在代码中的那个点。String...T...TFoo<T>Foo<String>fooTString

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

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

对于可能是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");
  }
}

这里的问题是,我们依赖于的类型,以便将其返回为 。但实际上,运行时参数的类型不是 的实例。argsT[]T[]T[]

3) 如果您的方法具有类型的参数(其中 T 是任何类型参数),则:T...

  • 安全:如果您的方法仅依赖于数组的元素是T
  • 不安全:如果它依赖于数组是T[]

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

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


答案 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
}

我发现我通常只添加varargs,以使我的呼叫者更方便。对于我的内部实现来说,使用 .因此,为了背负并确保我不可能引入堆污染,这就是我所做的。List<>Arrays.asList()

我知道这只能回答你的#3。newacct已经为上面的#1和#2给出了一个很好的答案,我没有足够的声誉来留下这个评论。:P