mirror of
https://github.com/sjsdfg/effective-java-3rd-chinese.git
synced 2025-03-29 21:50:14 +08:00
parent
45285d5d35
commit
0afc7e0a11
@ -43,7 +43,7 @@ String s = stringLists[0].get(0); // (5)
|
||||
|
||||
当你在强制转换为数组类型时,得到泛型数组创建错误,或是未经检查的强制转换警告时,最佳解决方案通常是使用集合类型 `List<E>` 而不是数组类型 `E[]`。 这样可能会牺牲一些简洁性或性能,但作为交换,你会获得更好的类型安全性和互操作性。
|
||||
|
||||
例如,假设你想用带有集合的构造方法来编写一个 `Chooser` 类,并且有个方法返回随机选择的集合的一个元素。 根据传递给构造方法的集合,可以使用选择器作为游戏模具,魔术 8 球或数据源进行蒙特卡罗模拟。 这是一个没有泛型的简单实现:
|
||||
例如,假设你想用带有集合的构造方法来编写一个 `Chooser` 类,并且有个方法返回随机选择的集合的一个元素。 根据传递给构造方法的集合,可以使用该类的实例对象作为游戏骰子,魔力 8 号球或蒙特卡罗模拟的数据源。 这是一个没有泛型的简单实现:
|
||||
|
||||
```java
|
||||
// Chooser - a class badly in need of generics!
|
||||
@ -63,7 +63,7 @@ public class Chooser {
|
||||
}
|
||||
```
|
||||
|
||||
要使用这个类,每次调用方法时,都必须将 `Object` 的 `choose` 方法的返回值转换为所需的类型,如果类型错误,则转换在运行时失败。 我们先根据条目 29 的建议,试图修改 `Chooser` 类,使其成为泛型的。
|
||||
要使用这个类,每次调用方法时,都必须将 `choose` 方法的返回值从 `Object` 转换为所需的类型,如果类型错误,则转换在运行时失败。 我们先根据条目 29 的建议,试图修改 `Chooser` 类,使其成为泛型的。
|
||||
|
||||
```java
|
||||
// A first cut at making Chooser generic - won't compile
|
||||
@ -130,5 +130,5 @@ public class Chooser<T> {
|
||||
|
||||
这个版本有些冗长,也许运行比较慢,但是值得一提的是,在运行时不会得到 `ClassCastException` 异常。
|
||||
|
||||
总之,数组和泛型具有非常不同的类型规则。 数组是协变和具体化的; 泛型是不变的,类型擦除的。 因此,数组提供运行时类型的安全性,但不提供编译时类型的安全性,反之亦然。 一般来说,数组和泛型不能很好地混合工作。 如果你发现把它们混合在一起,得到编译时错误或者警告,你的第一个冲动应该是用列表来替换数组。
|
||||
总之,数组和泛型具有非常不同的类型规则。 数组是协变和具体化的; 泛型是不变的,类型擦除的。 因此,数组提供运行时类型的安全性,但不提供编译时类型的安全性,对于泛型则是相反。 一般来说,数组和泛型不能很好地混合工作。 如果你发现把它们混合在一起,得到编译时错误或者警告,你的第一个冲动应该是用列表来替换数组。
|
||||
|
||||
|
@ -38,7 +38,7 @@ public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
|
||||
}
|
||||
```
|
||||
|
||||
至少对于简单的泛型方法来说,就是这样。 此方法编译时不会生成任何警告,并提供类型安全性和易用性。 这是一个简单的程序来运行该方法。 这个程序不包含强制转换和编译时没有错误或警告:至少对于简单的泛型方法来说,就是这样。 此方法编译时不会生成任何警告,并提供类型安全性和易用性。 这是一个简单的程序来运行该方法。 这个程序不包含强制转换和编译时没有错误或警告:
|
||||
至少对于简单的泛型方法来说,就是这样。 此方法编译时不会生成任何警告,并提供类型安全性和易用性。 这是一个简单的程序来运行该方法。 这个程序不包含强制转换,并且编译时没有错误或警告:
|
||||
|
||||
```java
|
||||
// Simple program to exercise generic method
|
||||
@ -54,7 +54,7 @@ public static void main(String[] args) {
|
||||
|
||||
`union` 方法的一个限制是所有三个集合(输入参数和返回值)的类型必须完全相同。 通过使用限定通配符类型( bounded wildcard types)(详见第 31 条),可以使该方法更加灵活。
|
||||
|
||||
有时,需要创建一个不可改变但适用于许多不同类型的对象。 因为泛型是通过擦除来实现的(详见第 28 条),所以可以使用单个对象进行所有必需的类型参数化,但是需要编写一个静态工厂方法来重复地为每个请求的类型参数化分配对象。 这种称为泛型单例工厂(generic singleton factory)的模式用于方法对象(function objects)(详见第 42 条),比如 `Collections.reverseOrder` 方法,偶尔也用于 `Collections.emptySet` 之类的集合。
|
||||
有时,需要创建一个不可改变但适用于许多不同类型的对象。 因为泛型是通过擦除来实现的(详见第 28 条),所以可以使用单个对象进行所有必需的类型参数化,但是需要编写一个静态工厂方法来重复地为每个请求的类型参数化分配对象。 这种被称为泛型单例工厂(generic singleton factory)的模式,用于函数对象(function objects)(详见第 42 条),比如 `Collections.reverseOrder` 方法,偶尔也用于 `Collections.emptySet` 之类的集合。
|
||||
|
||||
假设你想写一个恒等方法分配器( identity function dispenser)。 类库提供了 `Function.identity` 方法,所以没有理由编写你自己的实现(详见第 59 条),但它是有启发性的。 如果每次要求的时候都去创建一个新的恒等方法对象是浪费的,因为它是无状态的。 如果 Java 的泛型被具体化,那么每个类型都需要一个恒等方法,但是由于它们被擦除以后,所以泛型的单例就足够了。 以下是它的实例:
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 31. 使用限定通配符来增加 API 的灵活性
|
||||
|
||||
如条目 28 所述,参数化类型是不变的。换句话说,对于任何两个不同类型的 `Type1` 和 `Type`,`List<Type1>` 既不是 `List<Type2>` 子类型也不是其父类型。尽管 `List<String>` 不是 `List<Object>` 的子类型是违反直觉的,但它确实是有道理的。 可以将任何对象放入 `List<Object>` 中,但是只能将字符串放入 `List<String>` 中。 由于 `List<String>` 不能做 `List<Object>` 所能做的所有事情,所以它不是一个子类型(条目 10 中的里氏替代原则)。
|
||||
如条目 28 所述,参数化类型是不变的。换句话说,对于任何两个不同类型的 `Type1` 和 `Type2`,`List<Type1>` 既不是 `List<Type2>` 的子类型也不是其父类型。尽管 `List<String>` 不是 `List<Object>` 的子类型是违反直觉的,但它确实是有道理的。 可以将任何对象放入 `List<Object>` 中,但是只能将字符串放入 `List<String>` 中。 由于 `List<String>` 不能做 `List<Object>` 所能做的所有事情,所以它不是一个子类型(条目 10 中的里氏替代原则)。
|
||||
|
||||
相对于提供的不可变的类型,有时你需要比此更多的灵活性。 考虑条目 29 中的 `Stack` 类。下面是它的公共 API:
|
||||
|
||||
@ -68,7 +68,7 @@ public void popAll(Collection<E> dst) {
|
||||
}
|
||||
```
|
||||
|
||||
同样,如果目标集合的元素类型与栈的元素类型完全匹配,则干净编译并且工作正常。 但是,这又不完全令人满意。 假设你有一个 `Stac<Number>` 和 `Object` 类型的变量。 如果从栈中弹出一个元素并将其存储在该变量中,它将编译并运行而不会出错。 所以你也不能这样做吗?
|
||||
同样,如果目标集合的元素类型与栈的元素类型完全匹配,则干净编译并且工作正常。 但是,这又不完全令人满意。 假设你有一个 `Stac<Number>` 和 `Object` 类型的变量。 如果从栈中弹出一个元素并将其存储在该变量中,它将编译并运行而不会出错。 你不应该也这样做吗?
|
||||
|
||||
```java
|
||||
Stack<Number> numberStack = new Stack<Number>();
|
||||
@ -87,14 +87,14 @@ public void popAll(Collection<? super E> dst) {
|
||||
dst.add(pop());
|
||||
}
|
||||
```
|
||||
|
||||
通过这个改动,`Stack` 类和客户端代码都可以干净地编译。
|
||||
|
||||
通过这个改动,`Stack` 类和客户端代码都可以干净地编译。
|
||||
|
||||
这个结论很清楚。 **为了获得最大的灵活性,对代表生产者或消费者的输入参数使用通配符类型。** 如果一个输入参数既是一个生产者又是一个消费者,那么通配符类型对你没有好处:你需要一个精确的类型匹配,这就是没有任何通配符的情况。
|
||||
|
||||
这里有一个助记符来帮助你记住使用哪种通配符类型: **PECS 代表: producer-extends,consumer-super。**
|
||||
|
||||
换句话说,如果一个参数化类型代表一个 `T` 生产者,使用 `<? extends T>`;如果它代表 `T` 消费者,则使用 `<? super T>`。 在我们的 `Stack` 示例中,`pushAll` 方法的 `src` 参数生成栈使用的 `E` 实例,因此 `src` 的合适类型为 `Iterable<? extends E>`;`popAll` 方法的 `dst` 参数消费 `Stack` 中的 `E` 实例,因此 `dst` 的合适类型是 C`ollection <? super E>`。 PECS 助记符抓住了使用通配符类型的基本原则。 Naftalin 和 Wadler 称之为获取和放置原则(Get and Put Principle)[Naftalin07,2.4]。
|
||||
换句话说,如果一个参数化类型代表一个 `T` 生产者,使用 `<? extends T>`;如果它代表 `T` 消费者,则使用 `<? super T>`。 在我们的 `Stack` 示例中,`pushAll` 方法的 `src` 参数生成栈使用的 `E` 实例,因此 `src` 的合适类型为 `Iterable<? extends E>`;`popAll` 方法的 `dst` 参数消费 `Stack` 中的 `E` 实例,因此 `dst` 的合适类型是 `Collection <? super E>`。 PECS 助记符抓住了使用通配符类型的基本原则。 Naftalin 和 Wadler 称之为获取和放置原则(Get and Put Principle)[Naftalin07,2.4]。
|
||||
|
||||
记住这个助记符之后,让我们来看看本章中以前项目的一些方法和构造方法声明。 条目 28 中的 `Chooser` 类构造方法有这样的声明:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user