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

问题描述 投票:0回答:3

我最近遇到了 java

@SafeVarargs
注释。谷歌搜索 Java 中的可变参数函数不安全的原因让我相当困惑(堆中毒?擦除类型?),所以我想知道一些事情:

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

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

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

java java-7 variadic-functions
3个回答
284
投票

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
就可以了,因为我们知道
T
在代码中的那个点是
String

但是,当

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

这里的问题是,我们依赖

args
的类型为
T[]
才能将其返回为
T[]
。但实际上运行时参数的类型不是
T[]
的实例。

3) 如果你的方法有一个类型为

T...
的参数(其中 T 是任何类型参数),那么:

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

依赖于数组运行时类型的事情包括:将其作为类型

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

2)我上面提到的区分太复杂,无法轻松自动区分。


7
投票

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

如果你有这个:

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 已经为上面的 #1 和 #2 给出了很好的答案,但我没有足够的声誉来将此作为评论。 :P


0
投票

@SafeVarargs 用于表示方法不会造成堆污染。

堆污染是指我们在泛型数组中混合不同的参数化类型。

例如:

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

© www.soinside.com 2019 - 2024. All rights reserved.