泛型列表的数组

33

我正在尝试使用泛型和数组,以下代码似乎可以正常编译:

ArrayList<Key> a = new ArrayList<Key>();

但编译器会对这个发出警告:

ArrayList<Key>[] a = new ArrayList<Key>[10];

通过阅读stackoverflow上的帖子,我有点理解这是由于类型擦除导致的,而我可以通过使用以下方式来修复它:

ArrayList<Key>[] a = (ArrayList<Key> []) new ArrayList[10];

或者是列表的列表

ArrayList<ArrayList<Key>> b = new ArrayList<ArrayList<Key>>();

但我无法理解其中的原因。特别是第二种情况为什么不合法,而第一种情况却完全没问题。以及为什么编译器不会抱怨列表的列表。


3
无法在Java中创建一个链表数组。 - tcb
5个回答

24
由于数组需要原始类型,所以您无法拥有一个数组。在第二个实例中,您对其进行了类型转换,使其适合定义的类型,因此是合法的(但是,它无法推断)。列表的列表是合法的,因为 ArrayList 不是数组。
有关更多详细信息,请阅读official tutorial的第7.3章(第15页)。

The component type of an array object may not be a type variable or a parameterized type, unless it is an (unbounded) wildcard type.You can declare array types whose element type is a type variable or a parameterized type, but not array objects. This is annoying, to be sure. This restriction is necessary to avoid situations like:

List<String>[] lsa = new List<String>[10]; // not really allowed
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // unsound, but passes run time store check
String s = lsa[1].get(0); // run-time error - ClassCastException

If arrays of parameterized type were allowed, the example above would compile without any unchecked warnings, and yet fail at run-time.

教程随后指出:

由于类型变量在运行时不存在,因此无法确定实际的数组类型。 解决这类限制的方法是使用类字面值作为运行时类型标记。


6

数组是贫穷的茅屋,而使用真正的泛型时,应该避免使用数组,尽管这并不总是可能的。

数组是协变的,泛型是不变的;加上类型擦除,两者结合起来就不太适合,正如克里斯答案中所示的例子。

然而,我认为可以放宽规定以允许泛型数组的创建——实际上在那里没有问题。危险来自于向上转换数组;在那时编译器警告足矣。

事实上,Java确实为vararg方法创建泛型数组,因此有点虚伪。

以下是利用此事实的实用程序方法

@SafeVarargs
static <E> E[] arrayLiteral(E... array)
{
    return array;
}

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}

// usage

    List<String>[] array1 = arrayLiteral(list, list);

    List<String>[] array2 = newArray(10);

这可能是因为向后兼容性的原因。假设将来的某个版本(Java 9?)允许泛型数组创建,那么当Java 9代码与Java 5/6/7/8代码混合使用时(可以在没有警告的情况下向上转换数组),结果将是意想不到的ClassCastException。 - finnw
请注意,在这些代码示例中,创建的数组的实际类型不是 E[],而是某个推断类型 E 的擦除。例如,如果您执行了 static <T> T[] m() { T[] a = newArray(0); return a; },那么 a 的实际类型始终为 Object[],因为 E 的推断类型为 T,而 T 的擦除是 Object。如果有一些调用类似于 String[] a = m(); 的代码,它将抛出一个 ClassCastException - Radiodef

4

我自己也有一个类似的问题 - 就我个人而言,我没有发现答案有说服力的地方。来自最详细回答(参考pdf)的相关部分如下:

数组对象的组件类型可能不是类型变量或参数化类型,除非它是(无界)通配符类型。您可以声明元素类型为类型变量或参数化类型的数组类型,但不能声明数组对象。这很烦人,确实如此。这种限制是必要的,以避免像这样的情况

       List<String>[] lsa = new List<String>[10]; // not really allowed
       Object o = lsa;
       Object[] oa = (Object[]) o;
       List<Integer> li = new ArrayList<Integer>();
       li.add(new Integer(3));
       oa[1] = li; // unsound, but passes run time store check
       String s = lsa[1].get(0); // run-time error - ClassCastException

因为我可以将List[]转换为Object[],然后将错误的内容插入到Object[]中,然后通过强制转换的引用从List引用不正确地引用,所以这是不好/被禁止的吗?但只有使用new时才会出现这种情况?
对我来说,使用new声明与使用方式相比如何更少问题还是更多问题仍然有些晦涩,仍在盯着它看,希望它开始变得有意义,或者至少变成一个漂亮的3D图像。

编译器需要确保泛型类型的安全性;当无法保证时,它必须要么禁止代码运行,要么发出警告。在这个例子中,在第二行发出警告似乎已经足够了;Java 直接禁止泛型数组创建的决定似乎过于严厉。 - irreputable
1
是的,ArrayList<Key>[] a = (ArrayList<Key> []) new ArrayList<?>[10]; 仍然存在相同的问题,但它更清晰地表明你正在欺骗类型系统(并生成警告)。 - finnw
finnw,你的评论是我认为正确的答案,也是唯一让我感到有意义的方式。 - Steve B.

2

创建通用数组不是类型安全的(请参见 Joshua Bloch 的 "Effective Java - 第二版" 中的 "第25项:优先使用列表而不是数组")。

使用:

 List<List<Key>> b = new ArrayList<List<Key>>(10);

或者使用Java SE 7:

 List<List<Key>> b = new ArrayList<>(10);

1
数组允许避开类型检查(正如Chris的回答所示)。因此,您可以编写通过所有编译器检查的代码(没有编译器发出的“未经检查”的警告),但在运行时却会失败并抛出ClassCastException异常。
禁止这种构造方式会给开发人员带来问题,因此会出现警告。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接