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


175

我最近遇到了Java @SafeVarargs批注。搜寻是什么使Java中的可变参数功能不安全,这让我感到很困惑(堆中毒?已删除类型?),所以我想知道一些事情:

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

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

  3. 是否必须遵循一些标准以确保其功能确实是安全的?如果没有,有哪些最佳实践来确保呢?


1
您看过JavaDoc中的示例(和说明)吗?
jlordo

2
关于第三个问题,一个做法是始终有一个第一要素及其他:retType myMethod(Arg first, Arg... others)。如果不指定first,则允许使用空数组,并且很可能有一个名称相同,返回类型相同且不带参数的方法,这意味着JVM将很难确定应调用哪个方法。
fge13年

4
@jlordo我做到了,但是我不明白为什么它在varargs上下文中给出,因为它可以很容易地在varargs函数之外补充这种情况(经过验证,编译时会带有预期的类型安全警告和运行时错误)。–
Oren

Answers:


244

1)Internet和StackOverflow上有许多有关泛型和varargs特定问题的示例。基本上,这是当您拥有数量可变的类型参数类型的参数时:

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

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

当varargs类型为时,此方法效果很好String...。当它是类型变量(如)时T...,也可以在T已知为该调用的具体类型时使用。例如,如果上述方法是类的一部分Foo<T>,并且您有一个Foo<String>引用,则可以调用foo它,因为我们知道代码TString在那一点上。

但是,当of的“值” 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
    }
}

但是,对于任何依赖于所传递数组的运行时组件类型的事物,这都是不安全的。这是不安全并崩溃的简单示例:

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[],将其作为参数传递给type参数T[],使用获取数组类型.getClass(),将其传递给依赖于数组的运行时类型的方法,例如List.toArray()Arrays.copyOf()

2)我上面提到的区别太复杂了,以至于不能轻易自动区分。


7
好吧,这一切都说得通。谢谢。因此,为了让我完全理解您,可以说我们有一个类,FOO<T extends Something>将varargs方法的T []返回为Somthings 数组是否安全?
Oren

30
可能值得注意的是,使用(大概)总是正确的一种情况@SafeVarargs是,您对数组所做的唯一事情就是将其传递给已经被如此注释的另一种方法(例如:我经常发现自己写的vararg方法存在,可以使用将其参数转换为列表并将其Arrays.asList(...)传递给另一种方法;这种情况总是可以使用注释,@SafeVarargs因为Arrays.asList具有注释。
Jules 2014年

3
@newacct我不知道Java 7是否是这种情况,但是Java 8 @SafeVarargs除非方法是staticfinal否则是不允许的,因为否则它可能会在派生类中被覆盖而导致某些不安全的情况。
安迪

3
在Java 9中,也将允许使用private不能被覆盖的方法。
Dave L.

4

为了获得最佳实践,请考虑这一点。

如果您有这个:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.