第十二章 集合 添加原文脚注内容

This commit is contained in:
1326670425
2019-07-14 13:29:38 +08:00
parent 84f872317b
commit 7933dc0249

View File

@@ -17,12 +17,12 @@ MyType aReference
集合还有一些其它特性。例如, **Set** 对于每个值都只保存一个对象, **Map** 是一个关联数组允许将某些对象与其他对象关联起来。Java集合类都可以自动地调整自己的大小。因此与数组不同在编程时可以将任意数量的对象放置在集合中而不用关心集合应该有多大。
尽管在Java中没有直接的关键字支持集合类仍然是可以显著增强编程能力的基本工具。在本章中将介绍Java集合类库的基本知识并重点介绍一些典型用法。这里将专注于在日常编程中使用的集合。稍后在[附录:集合主题]()中,还将学习到其余的那些集合和相关功能,以及如何使用它们的更多详细信息。
尽管在Java中没有直接的关键字支持[^1]但集合类仍然是可以显著增强编程能力的基本工具。在本章中将介绍Java集合类库的基本知识并重点介绍一些典型用法。这里将专注于在日常编程中使用的集合。稍后在[附录:集合主题]()中,还将学习到其余的那些集合和相关功能,以及如何使用它们的更多详细信息。
<!-- Generics and Type-Safe Collections -->
## 泛型和类型安全的集合
使用Java SE5之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如考虑一个 **Apple** 对象的集合,这里使用最基本最可靠的 **ArrayList** 。现在,可以把 **ArrayList** 看作“可以自动扩充自身尺寸的数组”来看待。使用 **ArrayList** 相当简单:创建一个实例,用 **add()** 插入对象;然后用 **get()** 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。 **ArrayList** 还有一个 **size()** 方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出*运行时异常*[异常]()章节介绍了异常)。
使用Java SE5之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如考虑一个 **Apple** 对象的集合,这里使用最基本最可靠的 **ArrayList** 。现在,可以把 **ArrayList** 看作“可以自动扩充自身尺寸的数组”来看待。使用 **ArrayList** 相当简单:创建一个实例,用 **add()** 插入对象;然后用 **get()** 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。[^2] **ArrayList** 还有一个 **size()** 方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出*运行时异常*[异常]()章节介绍了异常)。
在本例中, **Apple****Orange** 都被放到了集合中然后将它们取出。正常情况下Java编译器会给出警告因为这个示例没有使用泛型。在这里使用特定的注解来抑制警告信息。注解以“@”符号开头,可以带参数。这里的 **@SuppressWarning** 注解及其参数表示只抑制“unchecked”类型的警告[注解]()章节将介绍更多有关注解的信息):
@@ -70,7 +70,7 @@ ndOrangesWithoutGenerics.java:23)
在[泛型]()章节中你将了解到使用Java泛型来创建类可能很复杂。但是使用预先定义的泛型类却相当简单。例如要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList<Apple>** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。
通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。下面还是这个示例,但是使用了泛型:
通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3]下面还是这个示例,但是使用了泛型:
```java
// collections/ApplesAndOrangesWithGenerics.java
import java.util.*;
@@ -517,7 +517,7 @@ public class SimpleIteration {
如果只是想向前遍历 **List** ,并不打算修改 **List** 对象本身,那么使用 *for-in* 语法更加简洁。
**Iterator** 还可以删除由 **next()** 生成的最后一个元素,这意味着在调用 **remove()** 之前必须先调用 **next()**
**Iterator** 还可以删除由 **next()** 生成的最后一个元素,这意味着在调用 **remove()** 之前必须先调用 **next()**[^4]
在集合中的每个对象上执行操作,这种思想十分强大,并且贯穿于本书。
@@ -1200,7 +1200,7 @@ B r o n t o s a u r u s
优先级队列声明下一个弹出的元素是最需要的元素具有最高的优先级。例如在机场当飞机临近起飞时这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息传递系统某些消息比其他消息更重要应该尽快处理而不管它们何时到达。在Java 5中添加了 **PriorityQueue** ,以便自动实现这种行为。
当在 **PriorityQueue** 上调用 **offer()** 方法来插入一个对象时,该对象会在队列中被排序。默认的排序使用队列中对象的*自然顺序*natural order但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用**peek()** **poll()****remove()** 方法时,获得的元素将是队列中优先级最高的元素。
当在 **PriorityQueue** 上调用 **offer()** 方法来插入一个对象时,该对象会在队列中被排序。[^5]默认的排序使用队列中对象的*自然顺序*natural order但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用**peek()** **poll()****remove()** 方法时,获得的元素将是队列中优先级最高的元素。
**PriorityQueue****Integer** **String****Character** 这样的内置类型一起工作易如反掌。在下面的示例中,第一组值与前一个示例中的随机值相同,可以看到它们从 **PriorityQueue** 中弹出的顺序与前一个示例不同:
@@ -1268,7 +1268,7 @@ C B A A
**Collection** 是所有序列集合共有的根接口。它可能会被认为是一种“附属接口”incidental interface即因为要表示其他若干个接口的共性而出现的接口。此外**java.util.AbstractCollection** 类提供了 **Collection** 的默认实现,使得你可以创建 **AbstractCollection** 的子类型,而其中没有不必要的代码重复。
使用接口描述的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。因此,如果所编写的方法接受一个 **Collection** ,那么该方法可以应用于任何实现了 **Collection** 的类——这也就使得一个新类可以选择去实现 **Collection** 接口以便该方法可以使用它。标准C++类库中的的集合并没有共同的基类——集合之间的所有共性都是通过迭代器实现的。在Java中遵循C++的方式看起来似乎很明智,即用迭代器而不是 **Collection** 来表示集合之间的共性。但是,这两种方法绑定在了一起,因为实现 **Collection** 就意味着需要提供 **iterator()** 方法:
使用接口描述的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。[^6]因此,如果所编写的方法接受一个 **Collection** ,那么该方法可以应用于任何实现了 **Collection** 的类——这也就使得一个新类可以选择去实现 **Collection** 接口以便该方法可以使用它。标准C++类库中的的集合并没有共同的基类——集合之间的所有共性都是通过迭代器实现的。在Java中遵循C++的方式看起来似乎很明智,即用迭代器而不是 **Collection** 来表示集合之间的共性。但是,这两种方法绑定在了一起,因为实现 **Collection** 就意味着需要提供 **iterator()** 方法:
```java
// collections/InterfaceVsIterator.java
@@ -1498,7 +1498,7 @@ public class EnvironmentVariables {
}
```
**System.getenv()** 返回一个 **Map** **entrySet()** 产生一个由 **Map.Entry** 的元素构成的 **Set** ,并且这个 **Set** 是一个 **Iterable** ,因此它可以用于*for-in*循环。
**System.getenv()** [^7]返回一个 **Map** **entrySet()** 产生一个由 **Map.Entry** 的元素构成的 **Set** ,并且这个 **Set** 是一个 **Iterable** ,因此它可以用于*for-in*循环。
*for-in*语句适用于数组或其它任何 **Iterable** ,但这并不意味着数组肯定也是个 **Iterable** ,也不会发生任何自动装箱:
@@ -1789,6 +1789,14 @@ Serializable]
尽管存在这些问题但Java集合仍是在日常工作中使用的基本工具它可以使程序更简洁、更强大、更有效。你可能需要一段时间才能熟悉集合类库的某些方面但我想你很快就会找到自己的路子来获得和使用这个类库中的类。
[^1]: 许多语言例如PerlPython和Ruby都有
集合的本地支持。
[^2]: 这里是操作符重载的用武之地C++和C#的集合类都使用操作符重载生成了更简洁的语法
[^3]: 在[泛型]()章节的末尾,有个关于这个问题是否很严重的讨论。但是,[泛型]()章节还将展示Java泛型远不止是类型安全的集合这么简单。
[^4]: **remove()** 是一个所谓的“可选”方法(还有一些其它的这种方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是标准Java库集合实现了 **remove()** ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。
[^5]: 这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个*堆*),但它们也可以在删除时选择最重要的元素。 如果对象的优先级在它在队列中等待时可以修改,那么算法的选择就显得很重要了。
[^6]: 有些人提倡这样一种自动创建机制,即对一个类中所有可能的方法组合都自动创建一个接口,有时候对于单个的类都是如此。 我相信接口的意义不应该仅限于方法组合的机械地复制,因此我在创建接口之前,总是要先看到增加接口带来的价值。
[^7]: 这在Java SE5之前是不可用的因为该方法被认为与操作系统的耦合度过紧因此违反“一次编写处处运行”的原则。现在提供它这一事实表明Java的设计者们更加务实了。
<!-- 分页 -->