mirror of
https://github.com/LingCoder/OnJava8.git
synced 2026-08-23 22:43:28 +08:00
@@ -22,9 +22,9 @@ MyType aReference;
|
||||
<!-- Generics and Type-Safe Collections -->
|
||||
## 泛型和类型安全的集合
|
||||
|
||||
使用 Java 5 之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如,考虑一个 **Apple** 对象的集合,这里使用最基本最可靠的 **ArrayList** 。现在,可以把 **ArrayList** 看作“可以自动扩充自身尺寸的数组”来看待。使用 **ArrayList** 相当简单:创建一个实例,用 **add()** 插入对象;然后用 **get()** 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。[^2] **ArrayList** 还有一个 **size()** 方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出*运行时异常*,[异常]()章节介绍了异常)。
|
||||
使用 Java 5 之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如,考虑一个 **Apple** 对象的集合,这里使用最基本最可靠的 **ArrayList** 。现在,可以把 **ArrayList** 看作“可以自动扩充自身尺寸的数组”来看待。使用 **ArrayList** 相当简单:创建一个实例,用 `add()` 插入对象;然后用 `get()` 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。[^2] **ArrayList** 还有一个 `size()` 方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出*运行时异常*,[异常]()章节介绍了异常)。
|
||||
|
||||
在本例中, **Apple** 和 **Orange** 都被放到了集合中,然后将它们取出。正常情况下,Java编译器会给出警告,因为这个示例没有使用泛型。在这里,使用特定的注解来抑制警告信息。注解以“@”符号开头,可以带参数。这里的 **@SuppressWarning** 注解及其参数表示只抑制“unchecked”类型的警告([注解]()章节将介绍更多有关注解的信息):
|
||||
在本例中, **Apple** 和 **Orange** 都被放到了集合中,然后将它们取出。正常情况下,Java编译器会给出警告,因为这个示例没有使用泛型。在这里,使用特定的注解来抑制警告信息。注解以“@”符号开头,可以带参数。这里的 `@SuppressWarning` 注解及其参数表示只抑制“unchecked”类型的警告([注解]()章节将介绍更多有关注解的信息):
|
||||
|
||||
```java
|
||||
// collections/ApplesAndOrangesWithoutGenerics.java
|
||||
@@ -64,11 +64,11 @@ ndOrangesWithoutGenerics.java:23)
|
||||
*/
|
||||
```
|
||||
|
||||
**Apple** 和 **Orange** 是截然不同的,它们除了都是 **Object** 之外没有任何共同点(如果一个类没有显式地声明继承自哪个类,那么它就自动继承自 **Object**)。因为 **ArrayList** 保存的是 **Object** ,所以不仅可以通过 **ArrayList** 的 **add()** 方法将 **Apple** 对象放入这个集合,而且可以放入 **Orange** 对象,这无论在编译期还是运行时都不会有问题。当使用 **ArrayList** 的 **get()** 方法来取出你认为是 **Apple** 的对象时,得到的只是 **Object** 引用,必须将其转型为 **Apple**。然后需要将整个表达式用括号括起来,以便在调用 **Apple** 的 **id()** 方法之前,强制执行转型。否则,将会产生语法错误。
|
||||
**Apple** 和 **Orange** 是截然不同的,它们除了都是 **Object** 之外没有任何共同点(如果一个类没有显式地声明继承自哪个类,那么它就自动继承自 **Object**)。因为 **ArrayList** 保存的是 **Object** ,所以不仅可以通过 **ArrayList** 的 `add()` 方法将 **Apple** 对象放入这个集合,而且可以放入 **Orange** 对象,这无论在编译期还是运行时都不会有问题。当使用 **ArrayList** 的 `get()` 方法来取出你认为是 **Apple** 的对象时,得到的只是 **Object** 引用,必须将其转型为 **Apple**。然后需要将整个表达式用括号括起来,以便在调用 **Apple** 的 `id()` 方法之前,强制执行转型。否则,将会产生语法错误。
|
||||
|
||||
在运行时,当尝试将 **Orange** 对象转为 **Apple** 时,会出现输出中显示的错误。
|
||||
|
||||
在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList<Apple>** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。
|
||||
在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList\<Apple>** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。
|
||||
|
||||
通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3]下面还是这个示例,但是使用了泛型:
|
||||
```java
|
||||
@@ -94,7 +94,7 @@ public class ApplesAndOrangesWithGenerics {
|
||||
*/
|
||||
```
|
||||
|
||||
在 **apples** 定义的右侧,可以看到 **new ArrayList<>()** 。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明,如下所示:
|
||||
在 **apples** 定义的右侧,可以看到 `new ArrayList<>()` 。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明,如下所示:
|
||||
|
||||
```java
|
||||
ArrayList<Apple> apples = new ArrayList<Apple>();
|
||||
@@ -104,7 +104,7 @@ ArrayList<Apple> apples = new ArrayList<Apple>();
|
||||
|
||||
有了 **ArrayList** 声明中的类型指定,编译器会阻止将 **Orange** 放入 **apples** ,因此,这会成为一个编译期错误而不是运行时错误。
|
||||
|
||||
使用泛型,从 **List** 中获取元素不需要强制类型转换。因为 **List** 知道它持有什么类型,因此当调用 **get()** 时,它会替你执行转型。这样,不仅可以知道编译器将会检查放入集合中的对象的类型,而且在使用集合中的对象时,可以使用更加清晰的语法。
|
||||
使用泛型,从 **List** 中获取元素不需要强制类型转换。因为 **List** 知道它持有什么类型,因此当调用 `get()` 时,它会替你执行转型。这样,不仅可以知道编译器将会检查放入集合中的对象的类型,而且在使用集合中的对象时,可以使用更加清晰的语法。
|
||||
|
||||
当指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以像作用于其他类型一样作用于泛型:
|
||||
```java
|
||||
@@ -137,7 +137,7 @@ Braeburn@4e25154f
|
||||
|
||||
因此,可以将 **Apple** 的子类型添加到被指定为保存 **Apple** 对象的集合中。
|
||||
|
||||
程序的输出是从 **Object** 默认的 **toString()** 方法产生的,该方法打印类名,后边跟着对象的散列码的无符号十六进制表示(这个散列码是通过 **hashCode()** 方法产生的)。将在[附录:理解equals和hashCode方法]()中了解有关散列码的内容。
|
||||
程序的输出是从 **Object** 默认的 `toString()` 方法产生的,该方法打印类名,后边跟着对象的散列码的无符号十六进制表示(这个散列码是通过 `hashCode()` 方法产生的)。将在[附录:理解equals和hashCode方法]()中了解有关散列码的内容。
|
||||
|
||||
<!-- Basic Concepts -->
|
||||
## 基本概念
|
||||
@@ -145,7 +145,7 @@ Braeburn@4e25154f
|
||||
Java集合类库采用“持有对象”(holding objects)的思想,并将其分为两个不同的概念,表示为类库的基本接口:
|
||||
|
||||
1. **集合(Collection)** :一个独立元素的序列,这些元素都服从一条或多条规则。**List** 必须以插入的顺序保存元素, **Set** 不能包含重复元素, **Queue** 按照*排队规则*来确定对象产生的顺序(通常与它们被插入的顺序相同)。
|
||||
2. **映射(Map)** : 一组成对的“键值对”对象,允许使用键来查找值。 **ArrayList** 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 **map**允许我们使用一个对象来查找另一个对象,它也被称作*关联数组*(associative array),因为它将对象和其它对象关联在一起;或者称作*字典*(dictionary),因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。 **Map**s是强大的编程工具。
|
||||
2. **映射(Map)** : 一组成对的“键值对”对象,允许使用键来查找值。 **ArrayList** 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 **map** 允许我们使用一个对象来查找另一个对象,它也被称作*关联数组*(associative array),因为它将对象和其它对象关联在一起;或者称作*字典*(dictionary),因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。 **Map** 是强大的编程工具。
|
||||
|
||||
尽管并非总是可行,但在理想情况下,你编写的大部分代码都在与这些接口打交道,并且唯一需要指定所使用的精确类型的地方就是在创建的时候。因此,可以像下面这样创建一个 **List** :
|
||||
|
||||
@@ -182,9 +182,9 @@ public class SimpleCollection {
|
||||
*/
|
||||
```
|
||||
|
||||
这个例子仅适用 **Collection** 方法,所以任何继承自 **Collection** 的类的对象都可以使用。但是 **ArrayList** 是最基本的序列类型。
|
||||
这个例子仅使用了 **Collection** 中的方法(即 `add()` ),所以使用任何继承自 **Collection** 的类的对象都可以正常工作。但是 **ArrayList** 是最基本的序列类型。
|
||||
|
||||
**add()** 方法的名称就表明它是在 **Collection** 中添加一个新元素。但是,文档中非常详细地叙述到 **add()** “要确保这个 **Collection** 包含指定的元素。”这是因为考虑到了 **Set** 的含义,因为在 **Set**中,只有当元素不存在时才会添加元素。在使用 **ArrayList** ,或任何其他类型的 **List** 时,**add()** 总是表示“把它放进去”,因为 **List** 不关心是否存在重复元素。
|
||||
`add()` 方法的名称就表明它是在 **Collection** 中添加一个新元素。但是,文档中非常详细地叙述到 `add()` “要确保这个 **Collection** 包含指定的元素。”这是因为考虑到了 **Set** 的含义,因为在 **Set**中,只有当元素不存在时才会添加元素。在使用 **ArrayList** ,或任何其他类型的 **List** 时,`add()` 总是表示“把它放进去”,因为 **List** 不关心是否存在重复元素。
|
||||
|
||||
可以使用 *for-in* 语法来遍历所有的 **Collection** ,就像这里所展示的那样。在本章的后续部分,还将学习到一个更灵活的概念,*迭代器*。
|
||||
|
||||
@@ -194,7 +194,7 @@ public class SimpleCollection {
|
||||
|
||||
在 **java.util** 包中的 **Arrays** 和 **Collections** 类中都有很多实用的方法,可以在一个 **Collection** 中添加一组元素。
|
||||
|
||||
**Arrays.asList()** 方法接受一个数组或是逗号分隔的元素列表(使用可变参数),并将其转换为 **List** 对象。 **Collections.addAll()** 方法接受一个 **Collection** 对象,以及一个数组或是一个逗号分隔的列表,将其中元素添加到 **Collection** 中。下边的示例展示了这两个方法,以及更通用的 **addAll()** 方法,所有 **Collection** 类型都包含该方法:
|
||||
`Arrays.asList()` 方法接受一个数组或是逗号分隔的元素列表(使用可变参数),并将其转换为 **List** 对象。 `Collections.addAll()` 方法接受一个 **Collection** 对象,以及一个数组或是一个逗号分隔的列表,将其中元素添加到 **Collection** 中。下边的示例展示了这两个方法,以及更通用的 `addAll()` 方法,所有 **Collection** 类型都包含该方法:
|
||||
|
||||
```java
|
||||
// collections/AddingGroups.java
|
||||
@@ -220,11 +220,11 @@ public class AddingGroups {
|
||||
}
|
||||
```
|
||||
|
||||
**Collection** 的构造器可以接受另一个 **Collection**,用它来将自身初始化。因此,可以使用 **Arrays.asList()** 来为这个构造器产生输入。但是, **Collections.addAll()** 运行得更快,而且很容易构建一个不包含元素的 **Collection** ,然后调用 **Collections.addAll()** ,因此这是首选方式。
|
||||
**Collection** 的构造器可以接受另一个 **Collection**,用它来将自身初始化。因此,可以使用 `Arrays.asList()` 来为这个构造器产生输入。但是, `Collections.addAll()` 运行得更快,而且很容易构建一个不包含元素的 **Collection** ,然后调用 `Collections.addAll()` ,因此这是首选方式。
|
||||
|
||||
**Collection.addAll()** 方法只能接受另一个 **Collection** 作为参数,因此它没有 **Arrays.asList()** 或 **Collections.addAll()** 灵活。这两个方法都使用可变参数列表。
|
||||
`Collection.addAll()` 方法只能接受另一个 **Collection** 作为参数,因此它没有 `Arrays.asList()` 或 `Collections.addAll()` 灵活。这两个方法都使用可变参数列表。
|
||||
|
||||
也可以直接使用 **Arrays.asList()** 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 **add()** 或 **delete()**,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误:
|
||||
也可以直接使用 `Arrays.asList()` 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 `add()` 或 `delete()`,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误:
|
||||
|
||||
```java
|
||||
// collections/AsListInference.java
|
||||
@@ -260,12 +260,12 @@ public class AsListInference {
|
||||
}
|
||||
```
|
||||
|
||||
在 **snow4** 中,注意 **Arrays.asList()** 中间的“hint”,告诉编译器 **Arrays.asList()** 生成的结果 **List** 类型的实际目标类型是什么。这称为*显式类型参数说明*(explicit type argument specification)。
|
||||
在 **snow4** 中,注意 `Arrays.asList()` 中间的“暗示”(即 `<Snow>` ),告诉编译器 `Arrays.asList()` 生成的结果 **List** 类型的实际目标类型是什么。这称为*显式类型参数说明*(explicit type argument specification)。
|
||||
|
||||
<!-- Printing Collections -->
|
||||
## 集合的打印
|
||||
|
||||
必须使用 **Arrays.toString()** 来生成数组的可打印形式。但是打印集合无需任何帮助。下面是一个例子,这个例子中也介绍了基本的Java集合:
|
||||
必须使用 `Arrays.toString()` 来生成数组的可打印形式。但是打印集合无需任何帮助。下面是一个例子,这个例子中也介绍了基本的Java集合:
|
||||
```java
|
||||
// collections/PrintingCollections.java
|
||||
// Collections print themselves automatically
|
||||
@@ -310,24 +310,23 @@ public class PrintingCollections {
|
||||
*/
|
||||
```
|
||||
|
||||
这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List**, 它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键*和与之关联的*值*。
|
||||
这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List** ,它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键*和与之关联的*值*。
|
||||
|
||||
默认的打印行为,使用集合提供的 `toString()` 方法即可生成可读性很好的结果。 **Collection** 打印出的内容用方括号括住,每个元素由逗号分隔。 **Map** 则由大括号括住,每个键和值用等号连接(键在左侧,值在右侧)。
|
||||
|
||||
第一个 **fill()** 方法适用于所有类型的 **Collection** ,这些类型都实现了 **add()** 方法以添加新元素。
|
||||
第一个 `fill()` 方法适用于所有类型的 **Collection** ,这些类型都实现了 `add()` 方法以添加新元素。
|
||||
|
||||
**ArrayList** 和 **LinkedList** 都是 **List** 的类型,从输出中可以看出,它们都按插入顺序保存元素。两者之间的区别不仅在于执行某些类型的操作时的性能,而且 **LinkedList** 包含的操作多于 **ArrayList** 。本章后面将对这些内容进行更全面的探讨。
|
||||
|
||||
**HashSet** , **TreeSet** 和 **LinkedHashSet** 是 **Set** 的类型。从输出中可以看到, **Set** 仅保存每个相同项中的一个,并且不同的 **Set** 实现存储元素的方式也不同。 **HashSet** 使用相当复杂的方法存储元素,这在[附录:集合主题]()中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此,存储顺序看上去没有什么意义(通常只关心某事物是否是 **Set** 的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用 **TreeSet** ,它将按比较结果的升序保存对象)或 **LinkedHashSet** ,它按照被添加的先后顺序保存对象。
|
||||
|
||||
**Map** (也称为*关联数组*)使用*键*来查找对象,就像一个简单的数据库。所关联的对象称为*值*。 假设有一个 **Map** 将美国州名与它们的首府联系在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作为键来查找,几乎就像使用数组下标一样。 这是由于这种行为,对于每个键, **Map** 只存储一次。
|
||||
|
||||
**Map.put(key, value)** 添加一个所想要添加的值并将它与一个键(用来查找值)相关联。 **Map.get(key)** 生成与该键相关联的值。上面的示例仅添加键值对,并没有执行查找。这将在稍后展示。
|
||||
**Map** (也称为*关联数组*)使用*键*来查找对象,就像一个简单的数据库。所关联的对象称为*值*。 假设有一个 **Map** 将美国州名与它们的首府联系在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作为键来查找,几乎就像使用数组下标一样。正是由于这种行为,对于每个键, **Map** 只存储一次。
|
||||
|
||||
`Map.put(key, value)` 添加一个所想要添加的值并将它与一个键(用来查找值)相关联。 `Map.get(key)` 生成与该键相关联的值。上面的示例仅添加键值对,并没有执行查找。这将在稍后展示。
|
||||
|
||||
请注意,这里没有指定(或考虑) **Map** 的大小,因为它会自动调整大小。 此外, **Map** 还知道如何打印自己,它会显示相关联的键和值。
|
||||
|
||||
本例使用了 **Map** 的三种基本风格: **HashMap** , **TreeMap**和 **LinkedHashMap** 。
|
||||
本例使用了 **Map** 的三种基本风格: **HashMap** , **TreeMap** 和 **LinkedHashMap** 。
|
||||
|
||||
键和值保存在 **HashMap** 中的顺序不是插入顺序,因为 **HashMap** 实现使用了非常快速的算法来控制顺序。 **TreeMap** 通过比较结果的升序来保存键, **LinkedHashMap** 在保持 **HashMap** 查找速度的同时按键的插入顺序保存键。
|
||||
|
||||
@@ -345,7 +344,7 @@ public class PrintingCollections {
|
||||
下面的示例导入 **typeinfo.pets** ,超前使用了[类型信息]()一章中的类库。这个类库包含了 **Pet** 类层次结构,以及用于随机生成 **Pet** 对象的一些工具类。此时不需要了解完整的详细信息,只需要知道两点:
|
||||
|
||||
1. 有一个 **Pet** 类,以及 **Pet** 的各种子类型。
|
||||
2. 静态的 **Pets.arrayList()** 方法返回一个填充了随机选取的 **Pet** 对象的 **ArrayList**:
|
||||
2. 静态的 `Pets.arrayList()` 方法返回一个填充了随机选取的 **Pet** 对象的 **ArrayList**:
|
||||
|
||||
```java
|
||||
// collections/ListFeatures.java
|
||||
@@ -442,44 +441,44 @@ sub: [Mouse, Pug]
|
||||
|
||||
打印行都编了号,因此可从输出追溯到源代码。 第 1 行输出展示了原始的由 **Pet** 组成的 **List** 。 与数组不同, **List** 可以在创建后添加或删除元素,并自行调整大小。这正是它的重要价值:一种可修改的序列。在第 2 行输出中可以看到添加一个 **Hamster** 的结果,该对象将被追加到列表的末尾。
|
||||
|
||||
可以使用 **contains()** 方法确定对象是否在列表中。如果要删除一个对象,可以将该对象的引用传递给 **remove()** 方法。同样,如果有一个对象的引用,可以使用 **indexOf()** 在 **List** 中找到该对象所在位置的下标号,如第 4 行输出所示中所示。
|
||||
可以使用 `contains()` 方法确定对象是否在列表中。如果要删除一个对象,可以将该对象的引用传递给 `remove()` 方法。同样,如果有一个对象的引用,可以使用 `indexOf()` 在 **List** 中找到该对象所在位置的下标号,如第 4 行输出所示中所示。
|
||||
|
||||
当确定元素是否是属于某个 **List** ,寻找某个元素的索引,以及通过引用从 **List** 中删除元素时,都会用到 **equals()** 方法(根类 **Object** 的一个方法)。每个 **Pet** 被定义为一个唯一的对象,所以即使列表中已经有两个 **Cymrics** ,如果再创建一个新的 **Cymric** 对象并将其传递给 **indexOf()** 方法,结果仍为 **-1** (表示未找到),并且尝试调用 **remove()** 方法来删除这个对象将返回 **false** 。对于其他类, **equals()** 的定义可能有所不同。例如,如果两个 **String** 的内容相同,则这两个 **String** 相等。因此,为了防止出现意外,请务必注意 **List** 行为会根据 **equals()** 行为而发生变化。
|
||||
当确定元素是否是属于某个 **List** ,寻找某个元素的索引,以及通过引用从 **List** 中删除元素时,都会用到 `equals()` 方法(根类 **Object** 的一个方法)。每个 **Pet** 被定义为一个唯一的对象,所以即使列表中已经有两个 **Cymrics** ,如果再创建一个新的 **Cymric** 对象并将其传递给 `indexOf()` 方法,结果仍为 **-1** (表示未找到),并且尝试调用 `remove()` 方法来删除这个对象将返回 **false** 。对于其他类, `equals()` 的定义可能有所不同。例如,如果两个 **String** 的内容相同,则这两个 **String** 相等。因此,为了防止出现意外,请务必注意 **List** 行为会根据 `equals()` 行为而发生变化。
|
||||
|
||||
第 7、8 行输出展示了删除与 **List** 中的对象完全匹配的对象是成功的。
|
||||
|
||||
可以在 **List** 的中间插入一个元素,就像在第 9 行输出和它之前的代码那样。但这会带来一个问题:对于 **LinkedList** ,在列表中间插入和删除都是廉价操作(在本例中,除了对列表中间进行的真正的随机访问),但对于 **ArrayList** ,这可是代价高昂的操作。这是否意味着永远不应该在 **ArrayList** 的中间插入元素,并最好是转换为 **LinkedList** ?不,它只是意味着你应该意识到这个问题,如果你开始在某个 **ArrayList** 中间进执行很多插入操作,并且程序开始变慢,那么你应该看看你的 **List** 实现有可能就是罪魁祸首(发现此类瓶颈的最佳方式是使用分析器 profiler)。优化是一个很棘手的问题,最好的策略就是置之不顾,直到发现必须要去担心它了(尽管去理解这些问题总是一个很好的主意)。
|
||||
可以在 **List** 的中间插入一个元素,就像在第 9 行输出和它之前的代码那样。但这会带来一个问题:对于 **LinkedList** ,在列表中间插入和删除都是廉价操作(在本例中,除了对列表中间进行的真正的随机访问),但对于 **ArrayList** ,这可是代价高昂的操作。这是否意味着永远不应该在 **ArrayList** 的中间插入元素,并最好是转换为 **LinkedList** ?不,它只是意味着你应该意识到这个问题,如果你开始在某个 **ArrayList** 中间执行很多插入操作,并且程序开始变慢,那么你应该看看你的 **List** 实现有可能就是罪魁祸首(发现此类瓶颈的最佳方式是使用分析器 profiler)。优化是一个很棘手的问题,最好的策略就是置之不顾,直到发现必须要去担心它了(尽管去理解这些问题总是一个很好的主意)。
|
||||
|
||||
**subList()** 方法可以轻松地从更大的列表中创建切片,当将切片结果传递给原来这个较大的列表的 **containsAll()** 方法时,很自然地会得到 **true**。请注意,顺序并不重要,在第 11、12 行输出中可以看到,在 **sub** 上调用直观命名的 **Collections.sort()** 和 **Collections.shuffle()** 方法,不会影响 **containsAll()** 的结果。 **subList()** 所产生的列表的幕后支持就是原始列表。因此,对所返回列表的更改都将会反映在原始列表中,反之亦然。
|
||||
`subList()` 方法可以轻松地从更大的列表中创建切片,当将切片结果传递给原来这个较大的列表的 `containsAll()` 方法时,很自然地会得到 **true**。请注意,顺序并不重要,在第 11、12 行输出中可以看到,在 **sub** 上调用直观命名的 `Collections.sort()` 和 `Collections.shuffle()` 方法,不会影响 `containsAll()` 的结果。 `subList()` 所产生的列表的幕后支持就是原始列表。因此,对所返回列表的更改都将会反映在原始列表中,反之亦然。
|
||||
|
||||
**retainAll()** 方法实际上是一个“集合交集”操作,在本例中,它保留了同时在 **copy** 和 **sub** 中的所有元素。请再次注意,所产生的结果行为依赖于 **equals()** 方法。
|
||||
`retainAll()` 方法实际上是一个“集合交集”操作,在本例中,它保留了同时在 **copy** 和 **sub** 中的所有元素。请再次注意,所产生的结果行为依赖于 `equals()` 方法。
|
||||
|
||||
第 14 行输出展示了使用索引号来删除元素的结果,与通过对象引用来删除元素相比,它显得更加直观,因为在使用索引时,不必担心 **equals()** 的行为。
|
||||
第 14 行输出展示了使用索引号来删除元素的结果,与通过对象引用来删除元素相比,它显得更加直观,因为在使用索引时,不必担心 `equals()` 的行为。
|
||||
|
||||
**removeAll()** 方法也是基于 **equals()** 方法运行的。 顾名思义,它会从 **List** 中删除在参数 **List** 中的所有元素。
|
||||
`removeAll()` 方法也是基于 `equals()` 方法运行的。 顾名思义,它会从 **List** 中删除在参数 **List** 中的所有元素。
|
||||
|
||||
**set()** 方法的命名显得很不合时宜,因为它与 **Set** 类存在潜在的冲突。在这里使用“replace”可能更适合,因为它的功能是用第二个参数替换索引处的元素(第一个参数)。
|
||||
`set()` 方法的命名显得很不合时宜,因为它与 **Set** 类存在潜在的冲突。在这里使用“replace”可能更适合,因为它的功能是用第二个参数替换索引处的元素(第一个参数)。
|
||||
|
||||
第 17 行输出表明,对于 **List** ,有一个重载的 **addAll()** 方法可以将新列表插入到原始列表的中间,而不仅仅只能用 **Collection** 的 **addAll()** 方法将追加到列表末尾。
|
||||
第 17 行输出表明,对于 **List** ,有一个重载的 `addAll()` 方法可以将新列表插入到原始列表的中间,而不仅仅只能用 **Collection** 的 `addAll()` 方法将追加到列表末尾。
|
||||
|
||||
第 18 - 20 行输出展示了 **isEmpty()** 和 **clear()** 方法的效果。
|
||||
第 18 - 20 行输出展示了 `isEmpty()` 和 `clear()` 方法的效果。
|
||||
|
||||
第 22、23 行输出展示了如何使用 **toArray()** 方法将任意的 **Collection** 转换为数组。这是一个重载方法,其无参版本返回一个 **Object** 数组,但是如果将目标类型的数组传递给这个重载版本,那么它会生成一个指定类型的数组(假设它通过了类型检查)。如果参数数组太小而无法容纳 **List** 中的所有元素(就像本例一样),则 **toArray()** 会创建一个具有合适尺寸的新数组。 **Pet** 对象有一个 **id()** 方法,可以在所产生的数组中的对象上调用这个方法。
|
||||
第 22、23 行输出展示了如何使用 `toArray()` 方法将任意的 **Collection** 转换为数组。这是一个重载方法,其无参版本返回一个 **Object** 数组,但是如果将目标类型的数组传递给这个重载版本,那么它会生成一个指定类型的数组(假设它通过了类型检查)。如果参数数组太小而无法容纳 **List** 中的所有元素(就像本例一样),则 `toArray()` 会创建一个具有合适尺寸的新数组。 **Pet** 对象有一个 `id()` 方法,可以在所产生的数组中的对象上调用这个方法。
|
||||
|
||||
<!-- Iterators -->
|
||||
|
||||
## 迭代器Iterators
|
||||
|
||||
在任何集合中,都必须有某种方式可以插入元素并再次获取它们。毕竟,保存事物是集合最基本的工作。对于 **List** , **add()** 是插入元素的一种方式, **get()** 是获取元素的一种方式。
|
||||
在任何集合中,都必须有某种方式可以插入元素并再次获取它们。毕竟,保存事物是集合最基本的工作。对于 **List** , `add()` 是插入元素的一种方式, `get()` 是获取元素的一种方式。
|
||||
|
||||
如果从更高层次的角度考虑,会发现这里有个缺点:要使用集合,必须对集合的确切类型编程。这一开始可能看起来不是很糟糕,但是考虑下面的情况:如果原本是对 **List** 编码的,但是后来发现如果能够将相同的代码应用于 **Set** 会更方便,此时应该怎么做?或者假设想从一开始就编写一段通用代码,它不知道或不关心它正在使用什么类型的集合,因此它可以用于不同类型的集合,那么如何才能不重写代码就可以应用于不同类型的集合?
|
||||
|
||||
*迭代器*(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为*轻量级对象*(lightweight object):创建它的代价小。因此,经常可以看到一些对迭代器有些奇怪的约束。例如,Java的 **Iterator** 只能单向移动。这个 **Iterator** 只能用来:
|
||||
*迭代器*(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为*轻量级对象*(lightweight object):创建它的代价小。因此,经常可以看到一些对迭代器有些奇怪的约束。例如,Java 的 **Iterator** 只能单向移动。这个 **Iterator** 只能用来:
|
||||
|
||||
1. 使用 **iterator()** 方法要求集合返回一个 **Iterator**。 **Iterator** 将准备好返回序列中的第一个元素。
|
||||
2. 使用 **next()** 方法获得序列中的下一个元素。
|
||||
3. 使用 **hasNext()** 方法检查序列中是否还有元素。
|
||||
4. 使用 **remove()** 方法将迭代器最近返回的那个元素删除。
|
||||
1. 使用 `iterator()` 方法要求集合返回一个 **Iterator**。 **Iterator** 将准备好返回序列中的第一个元素。
|
||||
2. 使用 `next()` 方法获得序列中的下一个元素。
|
||||
3. 使用 `hasNext()` 方法检查序列中是否还有元素。
|
||||
4. 使用 `remove()` 方法将迭代器最近返回的那个元素删除。
|
||||
|
||||
为了观察它的工作方式,这里再次使用[类型信息]()章节中的 **Pet** 工具:
|
||||
|
||||
@@ -519,15 +518,15 @@ public class SimpleIteration {
|
||||
*/
|
||||
```
|
||||
|
||||
有了 **Iterator** ,就不必再为集合中元素的数量操心了。这是由 **hasNext()** 和 **next()** 关心的事情。
|
||||
有了 **Iterator** ,就不必再为集合中元素的数量操心了。这是由 `hasNext()` 和 `next()` 关心的事情。
|
||||
|
||||
如果只是想向前遍历 **List** ,并不打算修改 **List** 对象本身,那么使用 *for-in* 语法更加简洁。
|
||||
|
||||
**Iterator** 还可以删除由 **next()** 生成的最后一个元素,这意味着在调用 **remove()** 之前必须先调用 **next()** 。[^4]
|
||||
**Iterator** 还可以删除由 `next()` 生成的最后一个元素,这意味着在调用 `remove()` 之前必须先调用 `next()` 。[^4]
|
||||
|
||||
在集合中的每个对象上执行操作,这种思想十分强大,并且贯穿于本书。
|
||||
|
||||
现在考虑创建一个 **display()** 方法,它不必知晓集合的确切类型:
|
||||
现在考虑创建一个 `display()` 方法,它不必知晓集合的确切类型:
|
||||
|
||||
```java
|
||||
// collections/CrossCollectionIteration.java
|
||||
@@ -565,7 +564,7 @@ public class CrossCollectionIteration {
|
||||
*/
|
||||
```
|
||||
|
||||
**display()** 方法不包含任何有关它所遍历的序列的类型信息。这也展示了 **Iterator** 的真正威力:能够将遍历序列的操作与该序列的底层结构分离。出于这个原因,我们有时会说:迭代器统一了对集合的访问方式。
|
||||
`display()` 方法不包含任何有关它所遍历的序列的类型信息。这也展示了 **Iterator** 的真正威力:能够将遍历序列的操作与该序列的底层结构分离。出于这个原因,我们有时会说:迭代器统一了对集合的访问方式。
|
||||
|
||||
我们可以使用 **Iterable** 接口生成上一个示例的更简洁版本,该接口描述了“可以产生 **Iterator** 的任何东西”:
|
||||
|
||||
@@ -606,12 +605,12 @@ public class CrossCollectionIteration2 {
|
||||
*/
|
||||
```
|
||||
|
||||
这里所有的类都是 **Iterable** ,所以现在对 **display()** 的调用显然更简单。
|
||||
这里所有的类都是 **Iterable** ,所以现在对 `display()` 的调用显然更简单。
|
||||
|
||||
<!-- ListIterator -->
|
||||
### ListIterator
|
||||
|
||||
**ListIterator** 是一个更强大的 **Iterator** 子类型,它只能由各种 **List** 类生成。虽然 **Iterator** 只能向前移动,但 **ListIterator** 可以双向移动。它还可以生成相对于迭代器在列表中指向的当前位置的后一个和前一个元素的索引,并且可以使用 **set()** 方法替换它访问过的最后一个元素。可以通过调用 **listIterator()** 方法来生成指向 **List** 开头处的 **ListIterator** ,还可以通过调用 **listIterator(n)** 创建一个一开始就指向列表索引号为 **n** 的元素处的 **ListIterator** 。 下面的示例演示了所有这些能力:
|
||||
**ListIterator** 是一个更强大的 **Iterator** 子类型,它只能由各种 **List** 类生成。 **Iterator** 只能向前移动,而 **ListIterator** 可以双向移动。它还可以生成相对于迭代器在列表中指向的当前位置的后一个和前一个元素的索引,并且可以使用 `set()` 方法替换它访问过的最近一个元素。可以通过调用 `listIterator()` 方法来生成指向 **List** 开头处的 **ListIterator** ,还可以通过调用 `listIterator(n)` 创建一个一开始就指向列表索引号为 **n** 的元素处的 **ListIterator** 。 下面的示例演示了所有这些能力:
|
||||
|
||||
```java
|
||||
// collections/ListIteration.java
|
||||
@@ -650,7 +649,7 @@ EgyptianMau]
|
||||
*/
|
||||
```
|
||||
|
||||
**Pets.get()** 方法用来从位置 3 开始替换 **List** 中的所有 Pet 对象。
|
||||
`Pets.get()` 方法用来从位置 3 开始替换 **List** 中的所有 Pet 对象。
|
||||
|
||||
<!-- LinkedList -->
|
||||
|
||||
@@ -660,11 +659,11 @@ EgyptianMau]
|
||||
|
||||
**LinkedList 还添加了一些方法,使其可以被用作栈、队列或双端队列(deque)** 。在这些方法中,有些彼此之间可能只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用(特别是在 **Queue** 中)。例如:
|
||||
|
||||
- **getFirst()** 和 **element()** 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 **List** 为空,则抛出 **NoSuchElementException** 异常。 **peek()** 方法与这两个方法只是稍有差异,它在列表为空时返回 **null** 。
|
||||
- **removeFirst()** 和 **remove()** 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 **NoSuchElementException** 异常。 **poll()** 稍有差异,它在列表为空时返回 **null** 。
|
||||
- **addFirst()** 在列表的开头插入一个元素。
|
||||
- **offer()** 与 **add()** 和 **addLast()** 相同。 它们都在列表的尾部(末尾)添加一个元素。
|
||||
- **removeLast()** 删除并返回列表的最后一个元素。
|
||||
- `getFirst()` 和 `element()` 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 **List** 为空,则抛出 **NoSuchElementException** 异常。 `peek()` 方法与这两个方法只是稍有差异,它在列表为空时返回 **null** 。
|
||||
- `removeFirst()` 和 `remove()` 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 **NoSuchElementException** 异常。 `poll()` 稍有差异,它在列表为空时返回 **null** 。
|
||||
- `addFirst()` 在列表的开头插入一个元素。
|
||||
- `offer()` 与 `add()` 和 `addLast()` 相同。 它们都在列表的尾部(末尾)添加一个元素。
|
||||
- `removeLast()` 删除并返回列表的最后一个元素。
|
||||
|
||||
下面的示例展示了这些功能之间基本的相似性和差异性。它并不是重复执行 **ListFeatures.java** 中所示的行为:
|
||||
|
||||
@@ -722,7 +721,7 @@ pets.removeLast(): Hamster
|
||||
*/
|
||||
```
|
||||
|
||||
**Pets.list()** 的结果被传递给 **LinkedList** 的构造器,以便使用它来填充 **LinkedList** 。如果查看 **Queue** 接口就会发现,它在 **LinkedList** 的基础上添加了 **element()** , **offer()** , **peek()** , **poll()** 和 **remove()** 方法,以使其可以成为一个 **Queue** 的实现。 **Queue** 的完整示例将在本章稍后给出。
|
||||
`Pets.list()` 的结果被传递给 **LinkedList** 的构造器,以便使用它来填充 **LinkedList** 。如果查看 **Queue** 接口就会发现,它在 **LinkedList** 的基础上添加了 `element()` , `offer()` , `peek()` , `poll()` 和 `remove()` 方法,以使其可以成为一个 **Queue** 的实现。 **Queue** 的完整示例将在本章稍后给出。
|
||||
|
||||
<!-- Stack -->
|
||||
|
||||
@@ -772,7 +771,7 @@ public class Stack<T> {
|
||||
}
|
||||
```
|
||||
|
||||
这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **<T>** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, **push()** 接受类型为 **T** 的对象,而 **peek()** 和 **pop()** 返回类型为 **T** 的对象。 **peek()** 方法将返回栈顶元素,但并不将其从栈顶删除,而 **pop()** 删除并返回顶部元素。
|
||||
这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **\<T>** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, `push()` 接受类型为 **T** 的对象,而 `peek()` 和 `pop()` 返回类型为 **T** 的对象。 `peek()` 方法将返回栈顶元素,但并不将其从栈顶删除,而 `pop()` 删除并返回顶部元素。
|
||||
|
||||
如果只需要栈的行为,那么使用继承是不合适的,因为这将产生一个具有 **ArrayDeque** 的其它所有方法的类(在[附录:集合主题]()中将会看到, **Java 1.0** 设计者在创建 **java.util.Stack** 时,就犯了这个错误)。使用组合,可以选择要公开的方法以及如何命名它们。
|
||||
|
||||
@@ -837,7 +836,7 @@ import onjava.Stack;
|
||||
|
||||
**Set** 不保存重复的元素。 如果试图将相同对象的多个实例添加到 **Set** 中,那么它会阻止这种重复行为。 **Set** 最常见的用途是测试归属性,可以很轻松地询问某个对象是否在一个 **Set** 中。因此,查找通常是 **Set** 最重要的操作,因此通常会选择 **HashSet** 实现,该实现针对快速查找进行了优化。
|
||||
|
||||
**Set** 具有与 **Collection** 相同的接口,因此没有任何额外的功能,不像前面两种不同类型的 **List** 那样。实际上, **Set** 就是一个 **Collection** ,只是行为不同。(这是继承和多态思想的典型应用:表现不同的行为。)**Set** 根据对象的“值”确定归属性,更复杂的问题将在[附录:集合主题]()中介绍。
|
||||
**Set** 具有与 **Collection** 相同的接口,因此没有任何额外的功能,不像前面两种不同类型的 **List** 那样。实际上, **Set** 就是一个 **Collection** ,只是行为不同。(这是继承和多态思想的典型应用:表现不同的行为。)**Set** 根据对象的“值”确定归属性,更复杂的内容将在[附录:集合主题]()中介绍。
|
||||
|
||||
下面是使用存放 **Integer** 对象的 **HashSet** 的示例:
|
||||
|
||||
@@ -862,7 +861,7 @@ public class SetOfInteger {
|
||||
|
||||
在 0 到 29 之间的 10000 个随机整数被添加到 **Set** 中,因此可以想象每个值都重复了很多次。但是从结果中可以看到,每一个数只有一个实例出现在结果中。
|
||||
|
||||
早期 Java 版本中的 **HashSet** 产生的输出没有可辨别的顺序。这是因为出于对速度的追求, **HashSet** 使用了散列,请参阅[附录:集合主题]()一章。由 **HashSet** 维护的顺序与 **TreeSet** 或 **LinkedHashSet** 不同,因为它们的实现具有不同的元素存储方式。 **TreeSet** 将元素存储在红-黑树数据结构中,而 **HashSet** 使用散列函数。 **LinkedHashSet** 因为查询速度的原因也使用了散列,但是看起来使用了链表来维护元素的插入顺序。显然,哈希算法已经更改,现在 **Integer** 按顺序排序。 但是,您不应该依赖此行为:
|
||||
早期 Java 版本中的 **HashSet** 产生的输出没有可辨别的顺序。这是因为出于对速度的追求, **HashSet** 使用了散列,请参阅[附录:集合主题]()一章。由 **HashSet** 维护的顺序与 **TreeSet** 或 **LinkedHashSet** 不同,因为它们的实现具有不同的元素存储方式。 **TreeSet** 将元素存储在红-黑树数据结构中,而 **HashSet** 使用散列函数。 **LinkedHashSet** 因为查询速度的原因也使用了散列,但是看起来使用了链表来维护元素的插入顺序。看起来散列算法好像已经改变了,现在 **Integer** 按顺序排序。但是,您不应该依赖此行为:
|
||||
|
||||
```java
|
||||
// collections/SetOfString.java
|
||||
@@ -916,7 +915,7 @@ public class SortedSetOfString {
|
||||
*/
|
||||
```
|
||||
|
||||
最常见的操作之一是使用 **contains()** 测试成员归属性,但也有一些其它操作,可能会让你想起在小学学过的维恩图(译者注:利用图形的交合表示多个集合之间的逻辑关系):
|
||||
最常见的操作之一是使用 `contains()` 测试成员归属性,但也有一些其它操作,这可能会让你想起在小学学过的维恩图(译者注:利用图形的交合表示多个集合之间的逻辑关系):
|
||||
|
||||
```java
|
||||
// collections/SetOperations.java
|
||||
@@ -958,9 +957,9 @@ Z]
|
||||
*/
|
||||
```
|
||||
|
||||
这些方法名都是自解释的,JDK文档中还有一些其它的方法。
|
||||
这些方法名都是自解释的,JDK 文档中还有一些其它的方法。
|
||||
|
||||
能够产生每个元素都唯一的列表是相当有用的功能。例如,假设想要列出上面的 **SetOperations.java** 文件中的所有单词,通过使用本书后面介绍的 **java.nio.file.Files.readAllLines()** 方法,可以打开一个文件,并将其作为一个 **List\<String\>** 读取,每个 **String** 都是输入文件中的一行:
|
||||
能够产生每个元素都唯一的列表是相当有用的功能。例如,假设想要列出上面的 **SetOperations.java** 文件中的所有单词,通过使用本书后面介绍的 `java.nio.file.Files.readAllLines()` 方法,可以打开一个文件,并将其作为一个 **List\<String>** 读取,每个 **String** 都是输入文件中的一行:
|
||||
|
||||
```java
|
||||
// collections/UniqueWords.java
|
||||
@@ -991,7 +990,7 @@ void]
|
||||
*/
|
||||
```
|
||||
|
||||
我们逐步浏览文件中的每一行,并使用 **String.split()** 将其分解为单词,这里使用正则表达式 **\\\ W +** ,这意味着它会依据一个或多个(即 **+** )非单词字母来拆分字符串(正则表达式将在[字符串]()章节介绍)。每个结果单词都会添加到 **Set words** 中。因为它是 **TreeSet** ,所以对结果进行排序。这里,排序是按*字典顺序*(lexicographically)完成的,因此大写和小写字母位于不同的组中。如果想按*字母顺序*(alphabetically)对其进行排序,可以向 **TreeSet** 构造器传入 **String.CASE_INSENSITIVE_ORDER** 比较器(比较器是一个建立排序顺序的对象):
|
||||
我们逐步浏览文件中的每一行,并使用 `String.split()` 将其分解为单词,这里使用正则表达式 **\\\ W +** ,这意味着它会依据一个或多个(即 **+** )非单词字母来拆分字符串(正则表达式将在[字符串]()章节介绍)。每个结果单词都会添加到 **Set words** 中。因为它是 **TreeSet** ,所以对结果进行排序。这里,排序是按*字典顺序*(lexicographically)完成的,因此大写和小写字母位于不同的组中。如果想按*字母顺序*(alphabetically)对其进行排序,可以向 **TreeSet** 构造器传入 **String.CASE_INSENSITIVE_ORDER** 比较器(比较器是一个建立排序顺序的对象):
|
||||
|
||||
```java
|
||||
// collections/UniqueWordsAlphabetic.java
|
||||
@@ -1058,9 +1057,9 @@ public class Statistics {
|
||||
*/
|
||||
```
|
||||
|
||||
- **[1]** 自动包装机制将随机生成的 **int** 转换为可以与 **HashMap** 一起使用的 **Integer** 引用(不能使用基本类型的集合)。如果键不在集合中,则 **get()** 返回 **null** (这意味着这是第一次找到该数字)。否则, **get()** 会为键生成与之关联的 **Integer** 值,然后该值被递增(自动包装机制再次简化了表达式,但实际上确实发生了对 **Integer** 的装箱和拆箱)。
|
||||
- **[1]** 自动包装机制将随机生成的 **int** 转换为可以与 **HashMap** 一起使用的 **Integer** 引用(不能使用基本类型的集合)。如果键不在集合中,则 `get()` 返回 **null** (这意味着这是第一次找到该数字)。否则, `get()` 会为键生成与之关联的 **Integer** 值,然后该值被递增(自动包装机制再次简化了表达式,但实际上确实发生了对 **Integer** 的装箱和拆箱)。
|
||||
|
||||
接下来的示例将使用一个 **String** 描述来查找 **Pet** 对象。它还展示了通过使用 **containsKey()** 和 **containsValue()** 方法去测试一个 **Map** ,以查看它是否包含某个键或某个值:
|
||||
接下来的示例将使用一个 **String** 描述来查找 **Pet** 对象。它还展示了通过使用 `containsKey()` 和 `containsValue()` 方法去测试一个 **Map** ,以查看它是否包含某个键或某个值:
|
||||
|
||||
```java
|
||||
// collections/PetMap.java
|
||||
@@ -1089,7 +1088,7 @@ true
|
||||
*/
|
||||
```
|
||||
|
||||
**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度,只需要创建一个值为 **Map** 的 **Map**(这些 **Map** 的值可以是其他集合,甚至是其他 **Map**)。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\<Person, List\<Pet\>\>** 即可:
|
||||
**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度,只需要创建一个值为 **Map** 的 **Map**(这些 **Map** 的值可以是其他集合,甚至是其他 **Map**)。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\<Person, List\<Pet>>** 即可:
|
||||
|
||||
```java
|
||||
|
||||
@@ -1157,7 +1156,7 @@ Person Luke has:
|
||||
*/
|
||||
```
|
||||
|
||||
**Map** 可以返回由其键组成的 **Set** ,由其值组成的 **Collection** ,或者其键值对的 **Set** 。 **keySet()** 方法生成由在 **petPeople** 中的所有键组成的 **Set** ,它在 *for-in* 语句中被用来遍历该 **Map** 。
|
||||
**Map** 可以返回由其键组成的 **Set** ,由其值组成的 **Collection** ,或者其键值对的 **Set** 。 `keySet()` 方法生成由在 **petPeople** 中的所有键组成的 **Set** ,它在 *for-in* 语句中被用来遍历该 **Map** 。
|
||||
|
||||
<!-- Queue -->
|
||||
|
||||
@@ -1196,9 +1195,9 @@ B r o n t o s a u r u s
|
||||
*/
|
||||
```
|
||||
|
||||
**offer()** 是与 **Queue** 相关的方法之一,它在允许的情况下,在队列的尾部插入一个元素,或者返回 **false** 。 **peek()** 和 **element()** 都返回队头元素而不删除它,但是如果队列为空,则 **element()** 抛出 **NoSuchElementException** ,而 **peek()** 返回 **null** 。 **poll()** 和 **remove()** 都删除并返回队头元素,但如果队列为空,**poll()** 返回 **null** ,而 **remove()** 抛出 **NoSuchElementException** 。
|
||||
`offer()` 是与 **Queue** 相关的方法之一,它在允许的情况下,在队列的尾部插入一个元素,或者返回 **false** 。 `peek()` 和 `element()` 都返回队头元素而不删除它,但是如果队列为空,则 `element()` 抛出 **NoSuchElementException** ,而 `peek()` 返回 **null** 。 `poll()` 和 `remove()`* 都删除并返回队头元素,但如果队列为空,`poll()` 返回 **null** ,而 `remove()` 抛出 **NoSuchElementException** 。
|
||||
|
||||
自动包装机制会自动将 **nextInt()** 的 **int** 结果转换为 **queue** 所需的 **Integer** 对象,并将 **char c** 转换为 **qc** 所需的 **Character** 对象。 **Queue** 接口窄化了对 **LinkedList** 方法的访问权限,因此只有适当的方法才能使用,因此能够访问到的 **LinkedList** 的方法会变少(这里实际上可以将 **Queue** 强制转换回 **LinkedList** ,但至少我们不鼓励这样做)。
|
||||
自动包装机制会自动将 `nextInt()` 的 **int** 结果转换为 **queue** 所需的 **Integer** 对象,并将 **char c** 转换为 **qc** 所需的 **Character** 对象。 **Queue** 接口窄化了对 **LinkedList** 方法的访问权限,因此只有适当的方法才能使用,因此能够访问到的 **LinkedList** 的方法会变少(这里实际上可以将 **Queue** 强制转换回 **LinkedList** ,但至少我们不鼓励这样做)。
|
||||
|
||||
与 **Queue** 相关的方法提供了完整而独立的功能。 也就是说,对于 **Queue** 所继承的 **Collection** ,在不需要使用它的任何方法的情况下,就可以拥有一个可用的 **Queue** 。
|
||||
|
||||
@@ -1209,7 +1208,7 @@ B r o n t o s a u r u s
|
||||
|
||||
优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。例如,在机场,当飞机临近起飞时,这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息传递系统,某些消息比其他消息更重要,应该尽快处理,而不管它们何时到达。在Java 5 中添加了 **PriorityQueue** ,以便自动实现这种行为。
|
||||
|
||||
当在 **PriorityQueue** 上调用 **offer()** 方法来插入一个对象时,该对象会在队列中被排序。[^5]默认的排序使用队列中对象的*自然顺序*(natural order),但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用**peek()** , **poll()** 或 **remove()** 方法时,获得的元素将是队列中优先级最高的元素。
|
||||
当在 **PriorityQueue** 上调用 `offer()` 方法来插入一个对象时,该对象会在队列中被排序。[^5]默认的排序使用队列中对象的*自然顺序*(natural order),但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用 `peek()` , `poll()` 或 `remove()` 方法时,获得的元素将是队列中优先级最高的元素。
|
||||
|
||||
让 **PriorityQueue** 与 **Integer** , **String** 和 **Character** 这样的内置类型一起工作易如反掌。在下面的示例中,第一组值与前一个示例中的随机值相同,可以看到它们从 **PriorityQueue** 中弹出的顺序与前一个示例不同:
|
||||
|
||||
@@ -1266,7 +1265,7 @@ C B A A
|
||||
*/
|
||||
```
|
||||
|
||||
**PriorityQueue** 是允许重复的,最小的值具有最高的优先级(如果是 **String** ,空格也可以算作值,并且比字母的优先级高)。为了展示如何通过提供自己的 **Comparator** 对象来改变顺序,第三个对 **PriorityQueue\<Integer\>** 构造器的调用,和第二个对 **PriorityQueue\<String\>** 的调用使用了由 **Collections.reverseOrder()** (Java 5 中新添加的)产生的反序的 **Comparator** 。
|
||||
**PriorityQueue** 是允许重复的,最小的值具有最高的优先级(如果是 **String** ,空格也可以算作值,并且比字母的优先级高)。为了展示如何通过提供自己的 **Comparator** 对象来改变顺序,第三个对 **PriorityQueue\<Integer>** 构造器的调用,和第二个对 **PriorityQueue\<String>** 的调用使用了由 `Collections.reverseOrder()` (Java 5 中新添加的)产生的反序的 **Comparator** 。
|
||||
|
||||
最后一部分添加了一个 **HashSet** 来消除重复的 **Character**。
|
||||
|
||||
@@ -1277,7 +1276,7 @@ C B A A
|
||||
|
||||
**Collection** 是所有序列集合共有的根接口。它可能会被认为是一种“附属接口”(incidental interface),即因为要表示其他若干个接口的共性而出现的接口。此外,**java.util.AbstractCollection** 类提供了 **Collection** 的默认实现,使得你可以创建 **AbstractCollection** 的子类型,而其中没有不必要的代码重复。
|
||||
|
||||
使用接口描述的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。[^6]因此,如果所编写的方法接受一个 **Collection** ,那么该方法可以应用于任何实现了 **Collection** 的类——这也就使得一个新类可以选择去实现 **Collection** 接口,以便该方法可以使用它。标准 C++ 类库中的的集合并没有共同的基类——集合之间的所有共性都是通过迭代器实现的。在 Java 中,遵循 C++ 的方式看起来似乎很明智,即用迭代器而不是 **Collection** 来表示集合之间的共性。但是,这两种方法绑定在了一起,因为实现 **Collection** 就意味着需要提供 **iterator()** 方法:
|
||||
使用接口描述的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。[^6]因此,如果所编写的方法接受一个 **Collection** ,那么该方法可以应用于任何实现了 **Collection** 的类——这也就使得一个新类可以选择去实现 **Collection** 接口,以便该方法可以使用它。标准 C++ 类库中的的集合并没有共同的基类——集合之间的所有共性都是通过迭代器实现的。在 Java 中,遵循 C++ 的方式看起来似乎很明智,即用迭代器而不是 **Collection** 来表示集合之间的共性。但是,这两种方法绑定在了一起,因为实现 **Collection** 就意味着需要提供 `iterator()` 方法:
|
||||
|
||||
```java
|
||||
// collections/InterfaceVsIterator.java
|
||||
@@ -1334,11 +1333,11 @@ Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx}
|
||||
*/
|
||||
```
|
||||
|
||||
两个版本的 **display()** 方法都可以使用 **Map** 或 **Collection** 的子类型来工作。 而且**Collection** 接口和 **Iterator** 都将 **display()** 方法与低层集合的特定实现解耦。
|
||||
两个版本的 `display()` 方法都可以使用 **Map** 或 **Collection** 的子类型来工作。 而且**Collection** 接口和 **Iterator** 都将 `display()` 方法与低层集合的特定实现解耦。
|
||||
|
||||
在本例中,这两种方式都可以奏效。事实上, **Collection** 要更方便一点,因为它是 **Iterable** 类型,因此在 **display(Collection)** 的实现中可以使用 *for-in* 构造,这使得代码更加清晰。
|
||||
在本例中,这两种方式都可以奏效。事实上, **Collection** 要更方便一点,因为它是 **Iterable** 类型,因此在 `display(Collection)` 的实现中可以使用 *for-in* 构造,这使得代码更加清晰。
|
||||
|
||||
当需要实现一个不是 **Collection** 的外部类时,由于让它去实现 **Collection** 接口可能非常困难或麻烦,因此使用 **Iterator** 就会变得非常吸引人。例如,如果我们通过继承一个持有 **Pet** 对象的类来创建一个 **Collection** 的实现,那么我们必须实现 **Collection** 所有的方法,即使我们不在 **display()** 方法中使用它们,也必须这样做。虽然这可以通过继承 **AbstractCollection** 而很容易地实现,但是无论如何还是要被强制去实现 **iterator()** 和 **size()** 方法,这些方法 **AbstractCollection** 没有实现,但是 **AbstractCollection** 中的其它方法会用到:
|
||||
当需要实现一个不是 **Collection** 的外部类时,由于让它去实现 **Collection** 接口可能非常困难或麻烦,因此使用 **Iterator** 就会变得非常吸引人。例如,如果我们通过继承一个持有 **Pet** 对象的类来创建一个 **Collection** 的实现,那么我们必须实现 **Collection** 所有的方法,即使我们不在 `display()` 方法中使用它们,也必须这样做。虽然这可以通过继承 **AbstractCollection** 而很容易地实现,但是无论如何还是要被强制去实现 `iterator()` 和 `size()` 方法,这些方法 **AbstractCollection** 没有实现,但是 **AbstractCollection** 中的其它方法会用到:
|
||||
|
||||
```java
|
||||
// collections/CollectionSequence.java
|
||||
@@ -1380,11 +1379,11 @@ extends AbstractCollection<Pet> {
|
||||
*/
|
||||
```
|
||||
|
||||
**remove()** 方法是一个“可选操作”,在[附录:集合主题]()中详细介绍。 这里可以不必实现它,如果你调用它,它将抛出异常。
|
||||
`remove()` 方法是一个“可选操作”,在[附录:集合主题]()中详细介绍。 这里可以不必实现它,如果你调用它,它将抛出异常。
|
||||
|
||||
- **[1]** 你可能会认为,因为 **iterator()** 返回 **Iterator\<Pet\>** ,匿名内部类定义可以使用菱形语法,Java可以推断出类型。但这不起作用,类型推断仍然非常有限。
|
||||
- **[1]** 你可能会认为,因为 `iterator()` 返回 **Iterator\<Pet>** ,匿名内部类定义可以使用菱形语法,Java可以推断出类型。但这不起作用,类型推断仍然非常有限。
|
||||
|
||||
这个例子表明,如果实现了 **Collection** ,就必须实现 **iterator()** ,并且只拿实现 **iterator()** 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能继承再 **AbstractCollection** 了。 在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。 此时,继承并提供创建迭代器的能力要容易得多:
|
||||
这个例子表明,如果实现了 **Collection** ,就必须实现 `iterator()` ,并且只拿实现 `iterator()` 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能继承再 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力要容易得多:
|
||||
|
||||
```java
|
||||
// collections/NonCollectionSequence.java
|
||||
@@ -1451,7 +1450,7 @@ public class ForInCollections {
|
||||
|
||||
由于 **cs** 是一个 **Collection** ,因此该代码展示了使用 *for-in* 是所有 **Collection** 对象的特征。
|
||||
|
||||
这样做的原因是 Java 5 引入了一个名为 **Iterable** 的接口,该接口包含一个能够生成 **Iterator** 的 **iterator()** 方法。*for-in* 使用此 **Iterable** 接口来遍历序列。因此,如果创建了任何实现了 **Iterable** 的类,都可以将它用于 *for-in* 语句中:
|
||||
这样做的原因是 Java 5 引入了一个名为 **Iterable** 的接口,该接口包含一个能够生成 **Iterator** 的 `iterator()` 方法。*for-in* 使用此 **Iterable** 接口来遍历序列。因此,如果创建了任何实现了 **Iterable** 的类,都可以将它用于 *for-in* 语句中:
|
||||
|
||||
```java
|
||||
// collections/IterableClass.java
|
||||
@@ -1488,7 +1487,7 @@ And that is how we know the Earth to be banana-shaped.
|
||||
*/
|
||||
```
|
||||
|
||||
**iterator()** 返回的是实现了 **Iterator\<String\>** 的匿名内部类的实例,该匿名内部类可以遍历数组中的每个单词。在 **main()** 中,可以看到 **IterableClass** 确实可以用于 *for-in* 语句。
|
||||
`iterator()` 返回的是实现了 **Iterator\<String>** 的匿名内部类的实例,该匿名内部类可以遍历数组中的每个单词。在主方法中,可以看到 **IterableClass** 确实可以用于 *for-in* 语句。
|
||||
|
||||
在 Java 5 中,许多类都是 **Iterable** ,主要包括所有的 **Collection** 类(但不包括各种 **Maps** )。 例如,下面的代码可以显示所有的操作系统环境变量:
|
||||
|
||||
@@ -1507,7 +1506,7 @@ public class EnvironmentVariables {
|
||||
}
|
||||
```
|
||||
|
||||
**System.getenv()** [^7]返回一个 **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** ,也不会发生任何自动装箱:
|
||||
|
||||
@@ -1539,7 +1538,7 @@ public class ArrayIsNotIterable {
|
||||
<!-- The Adapter Method Idiom -->
|
||||
### 适配器方法惯用法
|
||||
|
||||
如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢? 例如,假设你希望可以选择是在正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 **iterator()** 方法,则只能替换现有的方法,而不能实现选择。
|
||||
如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢? 例如,假设你希望可以选择是在正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 `iterator()` 方法,则只能替换现有的方法,而不能实现选择。
|
||||
|
||||
一种解决方案是所谓*适配器方法*(Adapter Method)的惯用法。“适配器”部分来自于设计模式,因为必须要提供特定的接口来满足 *for-in* 语句。如果已经有一个接口并且需要另一个接口时,则编写适配器就可以解决这个问题。
|
||||
在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用覆盖,相反,而是添加了一个能够生成 **Iterable** 对象的方法,该对象可以用于 *for-in* 语句。这使得我们可以提供多种使用 *for-in* 语句的方式:
|
||||
@@ -1592,7 +1591,7 @@ be to not or be To
|
||||
*/
|
||||
```
|
||||
|
||||
在 **main()** 中,如果直接将 **ral** 对象放在 *for-in* 语句中,则会得到(默认的)正向迭代器。但是如果在该对象上调用 **reversed()** 方法,它会产生不同的行为。
|
||||
在主方法中,如果直接将 **ral** 对象放在 *for-in* 语句中,则会得到(默认的)正向迭代器。但是如果在该对象上调用 `reversed()` 方法,它会产生不同的行为。
|
||||
|
||||
通过使用这种方式,可以在 **IterableClass.java** 示例中添加两种适配器方法:
|
||||
|
||||
@@ -1649,9 +1648,9 @@ And that is how we know the Earth to be banana-shaped.
|
||||
*/
|
||||
```
|
||||
|
||||
注意,第二个方法 **random()** 没有创建它自己的 **Iterator** ,而是直接返回被打乱的 **List** 中的 **Iterator** 。
|
||||
注意,第二个方法 `random()` 没有创建它自己的 **Iterator** ,而是直接返回被打乱的 **List** 中的 **Iterator** 。
|
||||
|
||||
从输出中可以看到, **Collections.shuffle()** 方法不会影响到原始数组,而只是打乱了 **shuffled** 中的引用。之所以这样,是因为 **randomized()** 方法用一个 **ArrayList** 将 **Arrays.asList()** 的结果包装了起来。如果这个由 **Arrays.asList()** 生成的 **List** 被直接打乱,那么它将修改底层数组,如下所示:
|
||||
从输出中可以看到, `Collections.shuffle()` 方法不会影响到原始数组,而只是打乱了 **shuffled** 中的引用。之所以这样,是因为 `randomized()` 方法用一个 **ArrayList** 将 `Arrays.asList()` 的结果包装了起来。如果这个由 `Arrays.asList()` 生成的 **List** 被直接打乱,那么它将修改底层数组,如下所示:
|
||||
|
||||
```java
|
||||
// collections/ModifyingArraysAsList.java
|
||||
@@ -1685,7 +1684,7 @@ array: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]
|
||||
*/
|
||||
```
|
||||
|
||||
在第一种情况下, **Arrays.asList()** 的输出被传递给了 **ArrayList** 的构造器,这将创建一个引用 **ia** 的元素的 **ArrayList** ,因此打乱这些引用不会修改该数组。但是,如果直接使用 **Arrays.asList(ia)** 的结果,这种打乱就会修改 **ia** 的顺序。重要的是要注意 **Arrays.asList()** 生成一个 **List** 对象,该对象使用底层数组作为其物理实现。如果执行的会修改这个 **List** ,并且不希望修改原始数组,那么就应该在另一个集合中创建一个副本。
|
||||
在第一种情况下, `Arrays.asList()` 的输出被传递给了 **ArrayList** 的构造器,这将创建一个引用 **ia** 的元素的 **ArrayList** ,因此打乱这些引用不会修改该数组。但是,如果直接使用 `Arrays.asList(ia)` 的结果,这种打乱就会修改 **ia** 的顺序。重要的是要注意 `Arrays.asList()` 生成一个 **List** 对象,该对象使用底层数组作为其物理实现。如果执行的操作会修改这个 **List** ,并且不希望修改原始数组,那么就应该在另一个集合中创建一个副本。
|
||||
|
||||
<!-- Summary -->
|
||||
## 本章小结
|
||||
@@ -1791,7 +1790,7 @@ Serializable]
|
||||
*/
|
||||
```
|
||||
|
||||
除 **TreeSet** 之外的所有 **Set** 都具有与 **Collection** 完全相同的接口。**List** 和 **Collection** 存在着明显的不同,尽管 **List** 所要求的方法都在 **Collection** 中。另一方面,在 **Queue** 接口中的方法是独立的,在创建具有 **Queue** 功能的实现时,不需要使用 **Collection** 方法。最后, **Map** 和 **Collection** 之间唯一的交集是 **Map** 可以使用 **entrySet()** 和 **values()** 方法来产生 **Collection** 。
|
||||
除 **TreeSet** 之外的所有 **Set** 都具有与 **Collection** 完全相同的接口。**List** 和 **Collection** 存在着明显的不同,尽管 **List** 所要求的方法都在 **Collection** 中。另一方面,在 **Queue** 接口中的方法是独立的,在创建具有 **Queue** 功能的实现时,不需要使用 **Collection** 方法。最后, **Map** 和 **Collection** 之间唯一的交集是 **Map** 可以使用 `entrySet()` 和 `values()` 方法来产生 **Collection** 。
|
||||
|
||||
请注意,标记接口 **java.util.RandomAccess** 附加到了 **ArrayList** 上,但不附加到 **LinkedList** 上。这为根据特定 **List** 动态改变其行为的算法提供了信息。
|
||||
|
||||
@@ -1799,19 +1798,19 @@ Serializable]
|
||||
|
||||
尽管存在这些问题,但 Java 集合仍是在日常工作中使用的基本工具,它可以使程序更简洁、更强大、更有效。你可能需要一段时间才能熟悉集合类库的某些方面,但我想你很快就会找到自己的路子,来获得和使用这个类库中的类。
|
||||
|
||||
[^1]: 许多语言,例如Perl,Python和Ruby,都有集合的本地支持。
|
||||
[^1]: 许多语言,例如 Perl ,Python 和 Ruby ,都有集合的本地支持。
|
||||
|
||||
[^2]: 这里是操作符重载的用武之地,C++和C#的集合类都使用操作符重载生成了更简洁的语法。
|
||||
|
||||
[^3]: 在[泛型]()章节的末尾,有个关于这个问题是否很严重的讨论。但是,[泛型]()章节还将展示Java泛型远不止是类型安全的集合这么简单。
|
||||
|
||||
[^4]: **remove()** 是一个所谓的“可选”方法(还有一些其它的这种方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准Java库集合实现了 **remove()** ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。
|
||||
[^4]: `remove()` 是一个所谓的“可选”方法(还有一些其它的这种方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准 Java 库集合实现了 `remove()` ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。
|
||||
|
||||
[^5]: 这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个*堆*),但它们也可以在删除时选择最重要的元素。 如果对象的优先级在它在队列中等待时可以修改,那么算法的选择就显得很重要了。
|
||||
|
||||
[^6]: 有些人提倡这样一种自动创建机制,即对一个类中所有可能的方法组合都自动创建一个接口,有时候对于单个的类都是如此。 我相信接口的意义不应该仅限于方法组合的机械地复制,因此我在创建接口之前,总是要先看到增加接口带来的价值。
|
||||
|
||||
[^7]: 这在Java 5之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在提供它这一事实表明,Java的设计者们更加务实了。
|
||||
[^7]: 这在 Java 5 之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在却提供它,这一事实表明, Java 的设计者们更加务实了。
|
||||
|
||||
<!-- 分页 -->
|
||||
|
||||
|
||||
Reference in New Issue
Block a user