java 警告:可变参数方法可能会因不可具体化的可变参数参数而导致堆污染

java warning: Varargs method could cause heap pollution from non-reifiable varargs parameter

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

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

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

IntelliJ IDEA 不会高亮显示上述代码中的任何内容作为警告。但是,在编译时,"Messages" 视图的 "Make" 选项卡中出现以下行:

Warning:(L, C) java: Varargs method could cause heap pollution from non-reifiable varargs parameter varargs

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

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

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

注意: Whosebug 上有很多关于可变参数方法的问题,但似乎有 none解决这个具体问题。事实上,整个 interwebz 似乎对这个特定问题的回答都很差。

需要额外的(而且看起来很多余)@SuppressWarnings( "varargs" ) 来抑制警告,如下所示:

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

事实上你不应该这样写你的代码。考虑以下示例:

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,因为您将整数放入 Collection<T> 数组。

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

您想要一个 Collection<Exception> 数组,但现在它包含一个 ArrayList<Integer>。这是非常危险的,因为您不会意识到存在问题。

这或许可以解释原因。我刚刚从 Effective Java 第 2 版第 25 条中复制了它。希望它能有所帮助。

The prohibition on generic array creation can be annoying. It means, for example, that it’s not generally possible for a generic type to return an array of its element type (but see Item 29 for a partial solution). It also means that you can get confusing warnings when using varargs methods (Item 42) in combination with generic types. This is because every time you invoke a varargs method, an array is created to hold the varargs parameters. If the element type of this array is not reifiable, you get a warning. There is little you can do about these warnings other than to suppress them (Item 24), and to avoid mixing generics and varargs in your APIs.

None 我在这个问题上看到的答案对我来说似乎是令人满意的,所以我想我会尝试一下。

这是我的看法:

  1. @SafeVarargs
  • 抑制警告:[unchecked] Possible heap pollution from parameterized vararg type Foo
  • 是方法的契约的一部分,因此为什么annotation has runtime retention.
  • 是对方法调用者的承诺,该方法不会使用通用可变参数参数弄乱堆。
  1. @SuppressWarnings("varargs")
  • 抑制警告:[varargs] Varargs method could cause heap pollution from non-reifiable varargs parameter bar
  • 是解决发生在方法代码中的问题,而不是方法的契约,因此为什么annotation only has source code retention
  • 告诉编译器它不需要担心方法代码调用的 callee 方法 使用非具体化可变参数参数产生的数组弄乱堆.

因此,如果我对 OP 的原始代码进行以下简单修改:

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

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

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 来消除该警告。这两者都使警告消失 ,通过向方法契约添加可变参数安全性,确保任何调用 bar 的人都不必抑制任何可变参数警告。

然而,它也使 Java 编译器更仔细地查看方法代码本身 - 我想是为了验证 bar() 可能违反我刚刚与@SafeVarargs。它看到 bar() 调用 baz() 传入 barArgs 和数字,因为 baz() 由于类型擦除而采用 Object[]baz() 可能会搞砸堆,从而导致 bar() 传递它。

所以我还需要将 @SuppressWarnings("varargs") 添加到 bar() 以使关于 bar() 代码的警告消失。

Assuming that I know what I am doing

我拒绝假设这一点,因为这似乎 难以置信地 错误。

您 运行 遇到的情况是,因为泛型不可具体化,所以您要声明一个泛型数组,这通常是不受欢迎的。鉴于数组是 协变的 String[]Object[]String 的方式相同,因此泛型和数组不能很好地混合是一个Object),而泛型是不变的List<String>不是一个List<Object>,即使StringObject).

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

final void varargsMethod(Collection<<Collection<? super T>> collections) { }