varargs堆污染:有什么大不了的?

Dic*_*ici 18 java memory generics variadic-functions heap-pollution

我正在阅读有关varargs堆污染的内容,我并不真正了解varargs或non-reifiable类型将如何解决那些在没有通用性的情况下尚未存在的问题.的确,我可以很容易地取代

public static void faultyMethod(List<String>... l) {
    Object[] objectArray = l; // Valid
    objectArray[0] = Arrays.asList(42);
    String s = l[0].get(0); // ClassCastException thrown here
}
Run Code Online (Sandbox Code Playgroud)

public static void faultyMethod(String... l) {
    Object[] objectArray = l; // Valid
    objectArray[0] = 42;  // ArrayStoreException thrown here
    String s = l[0];
}
Run Code Online (Sandbox Code Playgroud)

第二个只是使用数组的协方差,这实际上是问题所在.(即使List<String>是可以恢复的,我想它仍然是子类,Object我仍然可以将任何对象分配给数组.)当然我可以看到两者之间有一点差别,但是这个代码是错误的,无论是是否使用泛型.

它们对堆污染的意义是什么(它让我考虑内存使用情况,但他们谈到的唯一问题是潜在的类型不安全),它与使用数组协方差的任何类型违规有何不同?

ysh*_*vit 13

你是对的,常见的(和基本的)问题是数组的协方差.但是在你给出的那两个例子中,第一个更危险,因为可以修改你的数据结构并将它们置于一个稍后会破坏的状态.

考虑您的第一个示例是否未触发ClassCastException:

public static void faultyMethod(List<String>... l) {
  Object[] objectArray = l;           // Valid
  objectArray[0] = Arrays.asList(42); // Also valid
}
Run Code Online (Sandbox Code Playgroud)

以下是有人使用它的方式:

List<String> firstList = Arrays.asList("hello", "world");
List<String> secondList = Arrays.asList("hello", "dolly");
faultyMethod(firstList, secondList);
return secondList.isEmpty()
  ? firstList
  : secondList;
Run Code Online (Sandbox Code Playgroud)

所以现在我们有一个List<String>实际上包含一个Integer,并且它安全地浮动.在某些时候 - 可能更晚,如果它被序列化,可能晚以及在不同的JVM中 - 有人最终执行String s = theList.get(0).这种失败与导致它失败的原因相差甚远.

请注意,ClassCastException的堆栈跟踪不会告诉我们错误发生在哪里; 它只是告诉我们是谁触发了它.换句话说,它没有给我们提供有关如何修复bug的大量信息; 这就是使它成为一个比ArrayStoreException更大的交易.


Pet*_*rey 8

数组和List之间的区别在于数组检查它的引用.例如

Object[] array = new String[1];
array[0] = new Integer(1); // fails at runtime.
Run Code Online (Sandbox Code Playgroud)

然而

List list = new ArrayList<String>();
list.add(new Integer(1)); // doesn't fail.
Run Code Online (Sandbox Code Playgroud)

  • 是的,我知道这一点.我猜你的意思是数组被认为更安全,因为我无法在运行时欺骗他(`ArrayStoreException`),而`List`不会抱怨,因此他们在编译时会产生警告? (2认同)

Nay*_*uki 5

从链接文档中,我相信Oracle的"堆污染"意味着拥有JVM规范在技术上允许的数据值,但Java编程语言中的泛型规则不允许这样做.

举个例子,假设我们定义一个这样的简单List容器:

class List<E> {
    Object[] values;
    int len = 0;

    List() { values = new Object[10]; }

    void add(E obj) { values[len++] = obj; }
    E get(int i) { return (E)values[i]; }
}
Run Code Online (Sandbox Code Playgroud)

这是一个通用且安全的代码示例:

List<String> lst = new List<String>();
lst.add("abc");
Run Code Online (Sandbox Code Playgroud)

这是使用原始类型(绕过泛型)但仍然在语义级别遵守类型安全性的代码示例,因为我们添加的值具有兼容类型:

String x = (String)lst.values[0];
Run Code Online (Sandbox Code Playgroud)

扭曲 - 现在这里是与原始类型一起工作的代码,并做了一些坏事,导致"堆污染":

lst.values[lst.len++] = new Integer("3");
Run Code Online (Sandbox Code Playgroud)

上面的代码是有效的,因为数组是类型Object[],可以存储Integer.现在,当我们尝试检索该值时,它将导致ClassCastException- 在检索时(在发生损坏之后的方式),而不是在添加时间:

String y = lst.get(1);  // ClassCastException for Integer(3) -> String
Run Code Online (Sandbox Code Playgroud)

请注意,ClassCastException在我们当前的堆栈帧中发生,甚至没有List.get(),因为List.get()由于Java的类型擦除系统,在运行时转换为无操作.

基本上,我们通过绕过泛型来插入Integera List<String>.然后当我们尝试get()一个元素时,列表对象未能坚持它必须返回String(或null)的承诺.