我理解在使用带有泛型类型的varargs时,Java 7会出现这种情况;
但我的问题是......
当Eclipse说“它的使用可能会污染堆?”时,Eclipse究竟是什么意思?
并且
新的@SafeVarargs
注释如何阻止这种情况?
答案 0 :(得分:230)
堆污染是一个技术术语。它引用的引用的类型不是它们指向的对象的超类型。
List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // points to a list of As
这可能导致“无法解释的”ClassCastException
。
// if the heap never gets polluted, this should never throw a CCE
B b = listOfBs.get(0);
@SafeVarargs
根本不会阻止这种情况。但是,有些方法可以证明不会污染堆,编译器就是无法证明它。以前,这些API的调用者会得到令人讨厌的警告,这些警告完全没有意义,但必须在每个呼叫站点被抑制。现在API作者可以在声明站点禁止它一次。
但是,如果该方法实际上不安全,则不会再向用户发出警告。
答案 1 :(得分:214)
宣布
时 public static <T> void foo(List<T>... bar)
编译器将其转换为
public static <T> void foo(List<T>[] bar)
然后
public static <T> 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")
。有关第二种错误的详细解释,请参阅Is @SafeVarargs an appropriate annotation for this method?和https://stackoverflow.com/a/14252221/14731。
参考文献:
答案 2 :(得分:7)
@SafeVarargs
并不会阻止它发生,但它要求编译器在编译使用它的代码时更严格。
http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html更详细地解释了这一点。
堆污染是指在通用接口上执行操作时获得ClassCastException
并且它包含的其他类型而不是声明的。
答案 3 :(得分:5)
使用varargs时,可能会导致创建Object[]
来保存参数。
由于转义分析,JIT可以优化此阵列创建。 (我发现它的少数几次之一)它不能保证被优化掉,但除非你在内存分析器中看到它的问题,否则我不会担心它。
AFAIK @SafeVarargs
禁止编译器发出警告,并且不会改变JIT的行为方式。
答案 4 :(得分:1)
原因是因为varargs提供了使用非参数化对象数组调用的选项。因此,如果您的类型是List ...,则也可以使用List [] non-varargs类型调用它。
这里是一个例子:
public static void testCode(){
List[] b = new List[1];
test(b);
}
@SafeVarargs
public static void test(List<A>... a){
}
如您所见,List [] b可以包含任何类型的使用者,但是此代码可以编译。如果使用varargs,则可以,但是如果在类型擦除之后使用方法定义-void test(List [])-则编译器将不会检查模板参数类型。 @SafeVarargs将禁止显示此警告。
答案 5 :(得分:0)
当您可以控制方法的调用方式(例如类的私有方法)时,向方法添加 @SafeVarargs
注释是相当安全的。您必须确保仅将声明的泛型类型的实例传递给方法。
如果方法以库的形式对外公开,就很难发现这样的错误。在这种情况下,最好避免使用此注释并使用集合类型(例如 Collection<Type1<Type2>>
)输入而不是可变参数(Type1<Type2>...
)来重写解决方案。
至于命名,术语堆污染现象在我看来颇具误导性。在 documentation 中,没有提到实际的 JVM 堆 事件。软件工程中有一个question,其中包含一些关于此现象命名的有趣想法。