【问题标题】:Java SafeVarargs annotation, does a standard or best practice exist?Java SafeVarargs 注释,是否存在标准或最佳实践?
【发布时间】:2012-12-23 06:04:57
【问题描述】:

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

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

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

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

【问题讨论】:

  • 你看过JavaDoc中的例子(和解释)吗?
  • 对于第三个问题,一种做法是始终拥有第一个元素和其他元素:retType myMethod(Arg first, Arg... others)。如果不指定first,则允许使用空数组,并且您很可能有一个具有相同返回类型且不带参数的同名方法,这意味着 JVM 将很难确定应该使用哪个方法调用。
  • @jlordo 我做了,但我不明白为什么它在可变参数上下文中给出,因为人们可以在可变参数函数之外轻松复制这种情况(验证这一点,编译时会出现预期的类型安全警告和错误在运行时)..

标签: java java-7 variadic-functions


【解决方案1】:

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

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

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

当 varargs 类型像 String... 这样具体时,这很有效。当它是像 T... 这样的类型变量时,它也适用于已知 T 是该调用的具体类型时。例如如果上面的方法是 Foo&lt;T&gt; 类的一部分,并且你有一个 Foo&lt;String&gt; 引用,那么在它上面调用 foo 就可以了,因为我们知道 T 在代码中的那个点是 String

但是,当T 的“值”是另一个类型参数时,它不起作用。在 Java 中,不可能创建类型参数组件类型 (new T[] { ... }) 的数组。所以Java改用new Object[] { ... }(这里ObjectT的上限;如果有不同的上限,那就是Object而不是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)我上面提到的区分太复杂了,不容易自动区分。

【讨论】:

  • 现在一切都说得通了。谢谢。所以只是为了让我完全理解你,假设我们有一个类 FOO&lt;T extends Something&gt; 将可变参数方法的 T[] 作为 Somthings 的数组返回是否安全?
  • 可能值得注意的是,使用@SafeVarargs (大概)总是正确的一种情况是,您对数组所做的唯一事情就是将它传递给已经如此注释的另一个方法(例如:我经常发现自己编写可变参数方法,使用Arrays.asList(...) 将它们的参数转换为列表并将其传递给另一个方法;这种情况总是可以用@SafeVarargs 注释,因为Arrays.asList 有注释)。
  • @newacct 我不知道Java 7中是不是这样,但是Java 8不允许@SafeVarargs,除非方法是staticfinal,否则它可以在派生类中被不安全的东西覆盖。
  • 在 Java 9 中,private 方法也将被允许,这些方法也不能被覆盖。
【解决方案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
}

我发现我通常只添加可变参数以使调用者更方便。我的内部实现使用List&lt;&gt; 几乎总是更方便。所以要搭载Arrays.asList() 并确保我无法引入堆污染,这就是我要做的。

我知道这只会回答您的#3。 newacct 对上面的#1 和#2 给出了很好的答案,我没有足够的声誉将其作为评论留下。 :P

【讨论】:

    猜你喜欢
    • 2014-05-15
    • 1970-01-01
    • 1970-01-01
    • 2019-05-27
    • 1970-01-01
    • 2011-12-08
    • 2023-03-16
    • 2010-10-22
    • 2013-05-15
    相关资源
    最近更新 更多