Effective Java 第三版——28. 列表优于数组

Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。

Effective Java, Third Edition

数组在两个重要方面与泛型不同。 首先,数组是协变的(covariant)。 这个吓人的单词意味着如果Sub是Super的子类型,则数组类型Sub []是数组类型Super []的子类型。 相比之下,泛型是不变的(invariant):对于任何两种不同的类型Type1和Type2,List<Type1>既不是List <Type2>的子类型也不是父类型。[JLS,4.10; Naftalin07,2.5]。 你可能认为这意味着泛型是不足的,但可以说是数组缺陷。 这段代码是合法的:

// Fails at runtime! Object[] objectArray = new Long[1]; objectArray[0] = "I don't fit in"; // Throws ArrayStoreException

但这个不是:

// Won't compile! List<Object> ol = new ArrayList<Long>(); // Incompatible types ol.add("I don't fit in");

无论哪种方式,你不能把一个String类型放到一个Long类型容器中,但是用一个数组,你会发现在运行时产生了一个错误;对于列表,可以在编译时就能发现错误。 当然,你宁愿在编译时找出错误。

数组和泛型之间的第二个主要区别是数组被具体化了(reified)[JLS,4.7]。 这意味着数组在运行时知道并强制执行它们的元素类型。 如前所述,如果尝试将一个String放入Long数组中,得到一个ArrayStoreException异常。 相反,泛型通过擦除(erasure)来实现[JLS,4.6]。 这意味着它们只在编译时执行类型约束,并在运行时丢弃(或擦除)它们的元素类型信息。 擦除是允许泛型类型与不使用泛型的遗留代码自由互操作(条目 26),从而确保在Java 5中平滑过渡到泛型。

由于这些基本差异,数组和泛型不能很好地在一起混合使用。 例如,创建泛型类型的数组,参数化类型的数组,以及类型参数的数组都是非法的。 因此,这些数组创建表达式都不合法:new List <E> [],new List <String> [],new E []。 所有将在编译时导致泛型数组创建错误。

为什么创建一个泛型数组是非法的? 因为它不是类型安全的。 如果这是合法的,编译器生成的强制转换程序在运行时可能会因为ClassCastException异常而失败。 这将违反泛型类型系统提供的基本保证。

为了具体说明,请考虑下面的代码片段:

// Why generic array creation is illegal - won't compile! List<String>[] stringLists = new List<String>[1]; // (1) List<Integer> intList = List.of(42); // (2) Object[] objects = stringLists; // (3) objects[0] = intList; // (4) String s = stringLists[0].get(0); // (5)

让我们假设第1行创建一个泛型数组是合法的。第2行创建并初始化包含单个元素的List<Integer>。第3行将List<String>数组存储到Object数组变量中,这是合法的,因为数组是协变的。第4行将List <Integer>存储在Object数组的唯一元素中,这是因为泛型是通过擦除来实现的:List<Integer>实例的运行时类型仅仅是List,而List<String> []实例是List [],所以这个赋值不会产生ArrayStoreException异常。现在我们遇到了麻烦。将一个List<Integer>实例存储到一个声明为仅保存List<String>实例的数组中。在第5行中,我们从这个数组的唯一列表中检索唯一的元素。编译器自动将检索到的元素转换为String,但它是一个Integer,所以我们在运行时得到一个ClassCastException异常。为了防止发生这种情况,第1行(创建一个泛型数组)必须产生一个编译时错误。

类型E,List<E>和List<String>等在技术上被称为不可具体化的类型(nonreifiable types)[JLS,4.7]。 直观地说,不可具体化的类型是其运行时表示包含的信息少于其编译时表示的类型。 由于擦除,可唯一确定的参数化类型是无限定通配符类型,如List <?>和Map <?, ?>(条目 26)。 尽管很少有用,创建无限定通配符类型的数组是合法的。

禁止泛型数组的创建可能会很恼人的。 这意味着,例如,泛型集合通常不可能返回其元素类型的数组(但是参见条目 33中的部分解决方案)。 这也意味着,当使用可变参数方法(条目 53)和泛型时,会产生令人困惑的警告。 这是因为每次调用可变参数方法时,都会创建一个数组来保存可变参数。 如果此数组的元素类型不可确定,则会收到警告。 SafeVarargs注解可以用来解决这个问题(条目 32)。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyywgg.html