Tips

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

在这里第一时间翻译成中文版。供大家学习分享之用。

数组在两个重要方面与泛型不同。 首先,数组是协变的(covariant)。 这个吓人的单词意味着如果SubSuper的子类型,则数组类型Sub []是数组类型Super []的子类型。 相比之下,泛型是不变的(invariant):对于任何两种不同的类型Type1Type2List<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行(创建一个泛型数组)必须产生一个编译时错误。

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

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

当你在强制转换为数组类型时,得到泛型数组创建错误,或是未经检查的强制转换警告时,最佳解决方案通常是使用集合类型List <E>而不是数组类型E []。 这样可能会牺牲一些简洁性或性能,但作为交换,你会获得更好的类型安全性和互操作性。

例如,假设你想用带有集合的构造方法来编写一个Chooser类,并且有个方法返回随机选择的集合的一个元素。 根据传递给构造方法的集合,可以使用选择器作为游戏模具,魔术8球或数据源进行蒙特卡罗模拟。 这是一个没有泛型的简单实现:

// Chooser - a class badly in need of generics!
public class Chooser {
private final Object[] choiceArray; public Chooser(Collection choices) {
choiceArray = choices.toArray();
} public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}

要使用这个类,每次调用方法时,都必须将Object的choose方法的返回值转换为所需的类型,如果类型错误,则转换在运行时失败。 我们先根据条目 29的建议,试图修改Chooser类,使其成为泛型的。

// A first cut at making Chooser generic - won't compile
public class Chooser<T> {
private final T[] choiceArray; public Chooser(Collection<T> choices) {
choiceArray = choices.toArray();
} // choose method unchanged
}

如果你尝试编译这个类,会得到这个错误信息:

Chooser.java:9: error: incompatible types: Object[] cannot be
converted to T[]
choiceArray = choices.toArray();
^
where T is a type-variable:
T extends Object declared in class Chooser

没什么大不了的,将Object数组转换为T数组:

choiceArray = (T[]) choices.toArray();

这没有了错误,而是得到一个警告:

Chooser.java:9: warning: [unchecked] unchecked cast
choiceArray = (T[]) choices.toArray();
^
required: T[], found: Object[]
where T is a type-variable:
T extends Object declared in class Chooser

编译器告诉你在运行时不能保证强制转换的安全性,因为程序不会知道T代表什么类型——记住,元素类型信息在运行时会被泛型删除。 该程序可以正常工作吗? 是的,但编译器不能证明这一点。 你可以证明这一点,在注释中提出证据,并用注解来抑制警告,但最好是消除警告的原因(条目 27)。

要消除未经检查的强制转换警告,请使用列表而不是数组。 下面是另一个版本的Chooser类,编译时没有错误或警告:

// List-based Chooser - typesafe
public class Chooser<T> {
private final List<T> choiceList; public Chooser(Collection<T> choices) {
choiceList = new ArrayList<>(choices);
} public T choose() {
Random rnd = ThreadLocalRandom.current();
return choiceList.get(rnd.nextInt(choiceList.size()));
}
}

这个版本有些冗长,也许运行比较慢,但是值得一提的是,在运行时不会得到ClassCastException异常。

总之,数组和泛型具有非常不同的类型规则。 数组是协变和具体化的; 泛型是不变的,类型擦除的。 因此,数组提供运行时类型的安全性,但不提供编译时类型的安全性,反之亦然。 一般来说,数组和泛型不能很好地混合工作。 如果你发现把它们混合在一起,得到编译时错误或者警告,你的第一个冲动应该是用列表来替换数组。

Effective Java 第三版——28. 列表优于数组的更多相关文章

  1. Effective Java 第三版——18. 组合优于继承

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  2. Effective Java 第三版—— 20. 接口优于抽象类

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  3. Effective Java 第三版——39. 注解优于命名模式

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. Effective Java 第三版——65. 接口优于反射

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  5. 《Effective Java 第三版》目录汇总

    经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...

  6. 《Effective Java 第三版》新条目介绍

    版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...

  7. Effective Java 第三版——42.lambda表达式优于匿名类

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  8. Effective Java 第三版——43.方法引用优于lambda表达式

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. Effective Java 第三版——58. for-each循环优于传统for循环

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

随机推荐

  1. [js高手之路] es6系列教程 - Map详解以及常用api

    ECMAScript 6中的Map类型是一种存储着许多键值对的有序列表.键值对支持所有的数据类型. 键 0 和 ‘0’会被当做两个不同的键,不会发生强制类型转换. 如何使用Map? let map = ...

  2. appium-chromedriver@3.0.1 npm ERR! code ELIFECYCLE npm ERR! errno 1

    解决方法: npm install appium-chromedriver@3.0.1 --ignore-scripts 或者(安装方法): npm install appium-chromedriv ...

  3. mac 上安装服务,查看服务,重启和关闭

    首先了解下的Mac的 homebrew ,官网:https://brew.sh/index_zh-cn.html 简单的说: Homebrew 能干什么? 答:使用 Homebrew 安装 Apple ...

  4. python中的与 和 或

    上周五写程序碰到需要处理多重判断的一个逻辑,一般正确的写法是: if a or b or (c and d) or e: pass 因为变量很长,看上去比较杂乱,自己灵机一动写成了如下的样子: if ...

  5. 关于 " +new Date "

    关于 " +new Date " 的个人见解 今天晚上,在一个Javascript的Q群里,有人问下面这种代码是什么意思: var time = +new Date; 这段代码中, ...

  6. 没有robots.txt文件是否会影响收录呢

    Spider在抓取您的网站之前,会访问您的robots.txt 文件,以确定您的网站是否会阻止 蜘蛛抓取任何网页或网址.如果您的 robots.txt 文件存在但无法访问(也就是说,如果它没有返回 2 ...

  7. HDU 2546 饭卡(01背包裸题)

    饭卡 Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submiss ...

  8. BZOJ 1303: [CQOI2009]中位数图【前缀和】

    1303: [CQOI2009]中位数图 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 2737  Solved: 1698[Submit][Statu ...

  9. UVA11636-Hello World!-水题

    Hello World! Time limit: 1.000 seconds When you first made the computer to print the sentence "H ...

  10. Developing Skills

    题目传送门:点击打开链接 #include <iostream> #include <cstdio> #include <cstdlib> #include < ...