【问题标题】:java warning: Varargs method could cause heap pollution from non-reifiable varargs parameterjava 警告:可变参数方法可能会导致不可具体化可变参数参数的堆污染
【发布时间】:2015-05-08 23:04:17
【问题描述】:

我在 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 不会突出显示上述代码中的任何内容作为警告。但是,编译时,“Messages”视图的“Make”选项卡中会出现以下行:

警告:(L, C) java: Varargs 方法可能导致堆污染 不可具体化 varargs 参数 varargs

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

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

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

注意:stackoverflow 上有很多关于 varargs 方法的问题,但似乎没有一个可以解决这个特定问题。事实上,整个 interwebz 似乎对这个特定问题的回答都相当差。

【问题讨论】:

    标签: java generics intellij-idea variadic-functions


    【解决方案1】:

    我在这个问题上看到的答案都不令人满意,所以我想我会尝试一下。

    这是我的看法:

    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
    • 告诉编译器它不需要担心方法代码调用的被调用方法会使用由不可具体化的可变参数参数生成的数组弄乱堆。

    因此,如果我对 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() 代码的警告。

    【讨论】:

      【解决方案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&lt;T&gt; 数组。

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

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

      【讨论】:

      • Tony,感谢您深思熟虑的评论以及您根据我的代码提供工作代码所付出的努力,这表明了这种构造的危险,尽管它有点离题。当然我不会完全那样写代码,我总是立即将这些数组包装在不可修改的集合中,而数组元素又是其他不可修改的类,而不是Collection&lt;T&gt;,所以真的不会出错。为了方便起见,我只是在这里使用了Collection&lt;T&gt;,因为它已经存在于java.util 中,所以我不必在示例代码中提供额外的类。
      • 这取决于@SafeVarargs 的含义。我的感觉是@SafeVarargs 是一个承诺,即该方法不会对可变参数参数做任何不安全的事情,并且应该包括您传递此参数来处理它的方法。在这种解释下,如果arrayMethod 像您的示例那样对其进行了不安全的处理,那么您不应该为varargsMethod@SafeVarargs
      • 另外,在这种情况下,JLS 的哪一部分指定了警告?
      • 我只想说,@SafeVarargs 不能轻易使用,否则可能会出现细微的错误,导致任意行为。
      • @tony200910041:我看不出未经检查的转化是如何相关的。 varargsMethod 中没有未经检查的转换,您要抑制的警告是 varargs,而不是 unchecked
      【解决方案3】:

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

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

      【讨论】:

      • 这并不能解释为什么需要它。
      • @newacct 这个问题的新答案现在解释了它。
      【解决方案4】:

      假设我知道自己在做什么

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

      您在这里遇到的情况是,由于泛型不可具体化,因此您声明了一个泛型数组,这通常是不受欢迎的。鉴于数组是协变的String[]Object[]StringObject 相同),泛型和数组不能很好地混合,而泛型是不变的List&lt;String&gt;不是List&lt;Object&gt;,即使StringObject)。

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

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

      【讨论】:

        【解决方案5】:

        这或许可以解释原因。我刚刚从 Effective Java 2nd Edition 第 25 条中复制了它。希望它可以提供帮助。

        禁止创建泛型数组可能很烦人。例如,这意味着泛型类型通常不可能返回其元素类型的数组(但请参阅条款 29 以获得部分解决方案)。 这也意味着在将可变参数方法(第 42 条)与泛型类型结合使用时,您可能会收到令人困惑的警告。这是因为每次调用 varargs 方法时,都会创建一个数组来保存 varargs 参数。如果此数组的元素类型不可具体化,您会收到警告。 除了抑制它们(第 24 条)和避免在 API 中混合泛型和可变参数外,您几乎无法处理这些警告。

        【讨论】:

        • 但这与问题无关。当调用varargsMethod() 时,会在调用可变参数方法时创建通用数组。 (这就是为什么有一个@SafeVarargs。)但是问题中的警告发生在arrayMethod(),一个non-varargs方法被调用。
        • @newacct 基本上,“创建”泛型类型的数组是非法的,而方法参数 varargs 在 varargsMethod 中将导致创建 Collection 数组,因此它引发来自编译器的类型安全警告。另一方面,接受泛型类型数组的 arrayMethod() 不会导致创建任何泛型数组,因此它是合法的,根本不会引起任何警告。
        • @KinCheung:但就是这样。此处问题的警告调用arrayMethod(),而不是调用varargsMethod()
        • @newacct:对,我明白你现在的意思了。将 SafeVarargs 置于 varargsMethod() 之上后,我不再看到警告。我已经在 Eclipse 和 1.8 javac 中使用命令行进行了尝试。因此,关于对 arrayMethod() 的调用的警告不太可能与编译器有关。
        猜你喜欢
        • 1970-01-01
        • 2011-05-14
        • 2012-09-09
        • 1970-01-01
        • 2023-02-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多