我在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不会将上述代码中的任何内容突出显示为警告。但是,在编译时,“消息”视图的“生成”选项卡中会显示以下行:
警告:(L,C)java:Varargs方法可能会导致不可恢复的varargs参数varargs造成堆污染
注意#1:我已经指定了@SafeVarargs
。
注意#2:Warning:(L,C)
指向varargs
作为参数传递给arrayMethod()
假设我知道我在做什么,并且假设我非常确定不存在堆污染,或者我保证不会以某种可能导致堆污染的时髦方式调用此方法,我需要什么要压制此警告消息?
注意:有关varargs方法的stackoverflow存在大量问题,但似乎没有解决此特定问题的问题。事实上,整个interwebz对这个特定问题的答案似乎相当差。
这可能解释了原因。我刚从Effective Java第2版第25项中复制了它。希望它可以提供帮助。
禁止通用阵列创建可能很烦人。例如,这意味着泛型类型通常不可能返回其元素类型的数组(但是对于部分解决方案,请参见第29项)。这也意味着在将varargs方法(第42项)与泛型类型结合使用时,您可能会收到令人困惑的警告。这是因为每次调用varargs方法时,都会创建一个数组来保存varargs参数。如果此数组的元素类型不可恢复,则会收到警告。关于这些警告除了压制它们之外几乎没有什么可以做的(第24项),并且避免在API中混合使用泛型和变量。
我在这个问题上看到的答案似乎都不令人满意所以我认为我会抓住它。
这是我看到它的方式:
@SafeVarargs
是警告的治疗方法:[unchecked] Possible heap pollution from parameterized vararg type Foo
。
是方法合同的一部分,因此为什么annotation has runtime retention。
是方法调用者的承诺,该方法不会使用通用的varargs参数搞乱堆。@SuppressWarnings("varargs")
是警告的治疗方法:[varargs] Varargs method could cause heap pollution from non-reifiable varargs parameter bar
。
是解决方法代码中出现的问题,而不是方法的合同,因此为什么annotation only has source code retention。
告诉编译器它不需要担心方法代码调用的callee方法使用不可重新生成的varargs参数产生的数组搞乱堆。因此,如果我对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()
的代码发出警告。
实际上你不应该以这种方式编写代码。请考虑以下示例:
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>
。这是非常危险的,因为你不会意识到存在问题。
需要额外的(并且非常多余的)@SuppressWarnings( "varargs" )
来抑制警告,如下所示:
@SafeVarargs
@SuppressWarnings( "varargs" )
final void varargsMethod( Collection<T>... varargs )
{
arrayMethod( varargs );
}
假设我知道我在做什么
我拒绝承认这一点,因为这看起来非常错误。
你在这里遇到的情况是,因为泛型不可恢复,你要声明一个通用数组,这通常是不受欢迎的。泛型和数组不能很好地混合,因为数组是协变的(String[]
是Object[]
,就像String
是Object
一样),而泛型是不变的(List<String>
不是List<Object>
,即使String
是一个Object
)。
如果你想要一个集合集合......只需传递一个集合。它比混合数组和泛型更安全。
final void varargsMethod(Collection<<Collection<? super T>> collections) { }