通过可变参数参数可能造成堆污染

问题描述 投票:0回答:6

我知道在 Java 7 中使用泛型类型的可变参数时会出现这种情况;

但我的问题是..

Eclipse 说“它的使用可能会污染堆”到底是什么意思?

还有

新的

@SafeVarargs
注释如何防止这种情况发生?

java eclipse generics variadic-functions
6个回答
314
投票

堆污染是一个技术术语。它引用的类型不是它们所指向的对象的超类型。

List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // points to a list of As

这可能会导致“无法解释”

ClassCastException
s。

// if the heap never gets polluted, this should never throw a CCE
B b = listOfBs.get(0); 

@SafeVarargs
根本无法阻止这种情况。然而,有些方法可以证明不会污染堆,但编译器无法证明这一点。以前,此类 API 的调用者会收到恼人的警告,这些警告完全毫无意义,但必须在每个调用站点进行抑制。现在 API 作者可以在声明站点将其抑制一次。

但是,如果该方法实际上安全,则将不再警告用户。


298
投票

当你声明时

public static <T> void foo(List<T>... bar)
编译器将其转换为

public static <T> void foo(List<T>[] bar)
然后

public static void foo(List[] bar)

这样就会出现危险,您会错误地将不正确的值分配到列表中,并且编译器不会触发任何错误。例如,如果

T
String
那么下面的代码将正确编译,但在运行时会失败:

// First, strip away the array type (arrays allow this kind of upcasting)
Object[] objectArray = bar;

// Next, insert an element with an incorrect type into the array
objectArray[0] = Arrays.asList(new Integer(42));

// Finally, try accessing the original array. A runtime error will occur
// (ClassCastException due to a casting from Integer to String)
T firstElement = bar[0].get(0);

如果您检查了该方法以确保它不包含此类漏洞,那么您可以使用

@SafeVarargs
对其进行注释以抑制警告。对于接口,请使用
@SuppressWarnings("unchecked")

如果您收到此错误消息:

Varargs 方法可能会因不可具体化的 varargs 参数而导致堆污染

并且您确定您的使用是安全的,那么您应该使用

@SuppressWarnings("varargs")
来代替。有关第二种错误的详细解释,请参阅 @SafeVarargs 是否适合此方法?https://stackoverflow.com/a/14252221/14731

参考资料:

  • http://docs.oracle.com/javase/7/docs/technotes/guides/language/non-reativity-varargs.html
  • http://docs.oracle.com/javase/tutorial/java/generics/nonReABLEVarargsType.html#heap_pollution

11
投票

@SafeVarargs

并不能阻止这种情况的发生,但它要求编译器在编译使用它的代码时更加严格。 

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html更详细地解释了这一点。

堆污染是指在通用接口上执行操作时得到

ClassCastException

,并且它包含声明以外的其他类型。


7
投票
当您使用可变参数时,可能会导致创建一个

Object[]

 来保存参数。

由于逃逸分析,JIT 可以优化此数组创建。 (我发现它是少数几次)它不能保证被优化掉,但我不会担心它,除非你发现它是你的内存分析器中的问题。

AFAIK

@SafeVarargs

 抑制编译器发出警告,并且不会改变 JIT 的行为方式。


2
投票
原因是因为可变参数提供了使用非参数化对象数组调用的选项。因此,如果您的类型是 List

... ,也可以使用 List[] 非可变参数类型来调用。< A >

这是一个例子:

public static void testCode(){ List[] b = new List[1]; test(b); } @SafeVarargs public static void test(List<A>... a){ }

如您所见,List[] b 可以包含任何类型的使用者,但此代码可以编译。如果您使用可变参数,那么没问题,但如果您在类型擦除后使用方法定义 - void test(List[]) - 那么编译器将不会检查模板参数类型。 @SafeVarargs 将抑制此警告。


1
投票
当您可以控制方法的调用方式(例如类的私有方法)时,向方法添加

@SafeVarargs

 注释是相当安全的。您必须确保仅将声明的泛型类型的实例传递给该方法。

如果该方法作为库公开在外部,则很难捕获此类错误。在这种情况下,最好避免此注释并使用集合类型(例如

Collection<Type1<Type2>>

)输入而不是可变参数(
Type1<Type2>...
)重写解决方案。

就命名而言,“堆污染”现象在我看来是相当具有误导性的。在

文档中,甚至没有提到实际的 JVM 。软件工程中有一个问题,其中包含一些关于这种现象的命名的有趣想法。

© www.soinside.com 2019 - 2024. All rights reserved.