java警告:Varargs方法可能会导致来自不可恢复的varargs参数的堆污染

时间:2015-03-07 10:51:47

标签: java generics intellij-idea variadic-functions

我在JDK 1.8上使用IntelliJ IDEA和javac。我有以下代码:

class Test<T extends Throwable>
{
    @SafeVarargs
    final void varargsMethod( Collection<T>... varargs )
    {
        arrayMethod( varargs );
    }

    void arrayMethod( Collection<T>[] args )
    {
    }
}

IntelliJ IDEA不会将上述代码中的任何内容突出显示为警告。但是,在编译时,&#34; Make&#34; &#34;消息&#34;的标签;视图:

  

警告:(L,C)java:Varargs方法可能导致堆污染   不可恢复的varargs参数varargs

注意#1:我已经指定了@SafeVarargs

注意#2:Warning:(L,C)指向varargs作为参数传递给arrayMethod()

假设我知道我在做什么,并且假设我非常确定不存在堆污染,或者我保证不会以某种可能导致堆污染的时髦方式调用此方法, 我需要做些什么来抑制此警告消息?

注意: 有关varargs方法的stackoverflow存在大量问题,但似乎没有解决此特定问题的问题。事实上,整个interwebz对这个特定问题的回答似乎很差。

5 个答案:

答案 0 :(得分:9)

我在这个问题上看到的答案在我看来都不令人满意,所以我想我会捅它。

以下是我的看法:

  1. @SafeVarargs

    • 是警告的解决方法:[unchecked] Possible heap pollution from parameterized vararg type Foo
    • 是方法&#39> 合同的一部分,因此annotation has runtime retention
    • 的原因
    • 是方法调用者的一个承诺,该方法不会使用泛型varargs参数弄乱堆。
  2. @SuppressWarnings("varargs")

    • 是警告的解决方法:[varargs] Varargs method could cause heap pollution from non-reifiable varargs parameter bar
    • 可以解决方法代码 方法代码中出现的问题,而不是方法的合同,因此annotation only has source code retention
    • 告诉编译器它不需要担心方法代码调用的 callee方法使用不可重新生成的varargs参数产生的数组搞乱堆。
  3. 因此,如果我在OP的原始代码中采用以下简单的变体:

    class Foo {
        static <T> void bar(final T... barArgs) {
            baz(barArgs);
        }
        static <T> void baz(final T[] bazArgs) { }
    }
    

    使用Java 9.0.1编译器的$ javac -Xlint:all Foo.java输出为:

    Foo.java:2: warning: [unchecked] Possible heap pollution from parameterized vararg type T
        static <T> void bar(final T... barArgs) {
                                       ^
      where T is a type-variable:
        T extends Object declared in method <T>bar(T...)
    1 warning
    

    我可以通过将bar()标记为@SafeVarargs来消除警告。这通过向方法合同添加varargs安全性使警告消失,确保调用bar的任何人都不必禁止任何varargs警告。

    但是,它也使Java编译器更仔细地查看方法代码本身 - 我想是为了验证bar()可能违反我刚用@SafeVarargs签订的合同的简单情况。并且它看到bar()调用baz()传入barArgs并且因为baz()由于类型删除而导致Object[],因为baz()可能会陷入困境堆,从而导致bar()传递它。

    因此,我还需要将@SuppressWarnings("varargs")添加到bar(),以便对bar()代码的警告消失。

答案 1 :(得分:2)

需要额外的(并且非常多余的)@SuppressWarnings( "varargs" )来抑制警告,如下所示:

@SafeVarargs
@SuppressWarnings( "varargs" )
final void varargsMethod( Collection<T>... varargs )
{
    arrayMethod( varargs );
}

答案 2 :(得分:2)

实际上你不应该以这种方式编写代码。请考虑以下示例:

import java.util.*;

class Test<T extends Throwable>
{
    @SafeVarargs
    @SuppressWarnings("varargs")
    final void varargsMethod( Collection<T>... varargs )
    {
        arrayMethod( varargs );
    }

    void arrayMethod( Collection<T>[] args )
    {
        Object[] array = args;
        array[1] = new Integer(1);
        //
        //ArrayList<Integer> list = new ArrayList<>();
        //list.add(new Integer(1));
        //array[1] = list;
    }

    public static void main(String[] args)
    {
        ArrayList<Exception> list1 = new ArrayList<>();
        ArrayList<Exception> list2 = new ArrayList<>();
        (new Test<Exception>()).varargsMethod(list1, list2);
    }
}

如果运行代码,您将看到一个ArrayStoreException,因为您将一个Integer放入Collection<T>数组中。

但是,如果你替换array [1] = new Integer(1);由于类型擦除,使用三个注释行(即将ArrayList<Integer>放入数组中),不会抛出异常并且不会发生编译错误。

您希望拥有Collection<Exception>数组,但现在它包含ArrayList<Integer>。这是非常危险的,因为你不会意识到存在问题。

答案 3 :(得分:0)

  

假设我知道自己在做什么

我拒绝承担这一点,因为这似乎难以置信错误。

您遇到的情况是,由于泛型不可恢复,您要声明一个通用数组,这通常是不受欢迎的。鉴于数组协变String[]Object[],与String相同,泛型和数组不能很好地混合一个Object),而泛型不变List<String> 一个List<Object>,即使StringObject final void varargsMethod(Collection<<Collection<? super T>> collections) { } )。

如果你想要一个集合的集合......只需传递一个。它比混合数组和泛型更安全。

propertyItem

答案 4 :(得分:-3)

这可能解释了原因。我刚从Effective Java第2版第25项中复制了它。希望它可以提供帮助。

  

禁止通用阵列创建可能很烦人。例如,这意味着泛型类型通常不可能返回其元素类型的数组(但是对于部分解决方案,请参见第29项)。 这也意味着当将varargs方法(第42项)与泛型类型结合使用时,您可能会收到令人困惑的警告。这是因为每次调用varargs方法时,都会创建一个数组来保存varargs参数。如果此数组的元素类型不可恢复,则会收到警告。除了禁止这些警告之外,您几乎无法做到这些警告(第24项),并避免在API中混合泛型和变量。