Skip to content

Commit e41f1e9

Browse files
maskleogitbook-bot
maskleo
authored andcommitted
GitBook: [gh-pages] 104 pages and 33 assets modified
1 parent 904c200 commit e41f1e9

File tree

137 files changed

+8495
-105
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

137 files changed

+8495
-105
lines changed

Diff for: .gitbook/assets/05_1.png

43.9 KB
Loading

Diff for: .gitbook/assets/10_1.png

64.5 KB
Loading

Diff for: .gitbook/assets/12_1.png

62 KB
Loading

Diff for: .gitbook/assets/13_0.png

36.4 KB
Loading

Diff for: .gitbook/assets/13_1.png

18.3 KB
Loading

Diff for: .gitbook/assets/13_2.png

19.2 KB
Loading

Diff for: .gitbook/assets/13_3.png

29.6 KB
Loading

Diff for: .gitbook/assets/13_4.png

53.7 KB
Loading

Diff for: .gitbook/assets/13_5.png

22.2 KB
Loading

Diff for: .gitbook/assets/13_6.png

9.04 KB
Loading

Diff for: .gitbook/assets/13_7.png

33.2 KB
Loading

Diff for: .gitbook/assets/13_8.png

45.6 KB
Loading

Diff for: .gitbook/assets/14_1.png

15.5 KB
Loading

Diff for: .gitbook/assets/14_2.png

65.3 KB
Loading

Diff for: .gitbook/assets/14_3.png

20.4 KB
Loading

Diff for: .gitbook/assets/14_4.png

20.9 KB
Loading

Diff for: .gitbook/assets/14_5.png

37.5 KB
Loading

Diff for: .gitbook/assets/14_6.png

47.6 KB
Loading

Diff for: .gitbook/assets/14_7.png

77.1 KB
Loading

Diff for: .gitbook/assets/14_8.png

28.4 KB
Loading

Diff for: .gitbook/assets/14_9.png

40.5 KB
Loading

Diff for: .gitbook/assets/15_1 (1).png

47.3 KB
Loading

Diff for: .gitbook/assets/15_1.png

47.3 KB
Loading

Diff for: .gitbook/assets/15_3.png

29.7 KB
Loading

Diff for: .gitbook/assets/15_4.png

34.7 KB
Loading

Diff for: .gitbook/assets/16_1.png

49.8 KB
Loading

Diff for: .gitbook/assets/16_2.png

53 KB
Loading

Diff for: .gitbook/assets/16_3.png

40.2 KB
Loading

Diff for: .gitbook/assets/16_4.png

38.9 KB
Loading

Diff for: .gitbook/assets/16_5.png

35.3 KB
Loading

Diff for: .gitbook/assets/16_6.png

96 KB
Loading

Diff for: .gitbook/assets/16_7.png

64.7 KB
Loading

Diff for: .gitbook/assets/license.png

1.77 KB
Loading

Diff for: SUMMARY.md

+105-105
Large diffs are not rendered by default.

Diff for: di-er-bu-fen-ji-he/README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# 第二部分:集合
2+
3+
《《《 [返回首页](../)
4+
《《《 [上一节](../di-yi-bu-fen-fan-xing/di-jiu-zhang-she-ji-mo-shi/9.5-zhu-ti-guan-cha-zhe.md)
5+
6+
## 集合
7+
8+
`Java` 集合框架是 `java.util` 和 `java.util.concurrent` 包中的一组接口和类。他们为客户程序提供了如何组织其对象的各种模型以及每种模型的各种实现。这些模型有时被称为抽象数据类型,我们需要它们,因为不同的程序需要不同的方式来组织它们的对象。在一种情况下,您可能希望将程序的对象组织在顺序列表中,因为它们的排序很重要,并且有重复。另一种情况是,一个集合可能是正确的数据类型,因为现在的排序并不重要,你想放弃重复。这两种数据类型(以及其他)由集合框架中的不同接口表示,我们将在本章中查看它们的使用示例。但那不是全部;这些数据类型中没有一个具有单一的“最佳”实现 - 也就是说,对于所有操作,其中一个实现比所有其他实现更好。例如,链接列表可能比用于从中间插入和删除元素的列表的阵列实现更好,但是对于随机访问更糟糕。因此,为您的程序选择正确的实施方式涉及知道如何使用以及可用的方式。
9+
10+
本书的这一部分首先概述框架,然后详细介绍每个主界面及其标准实现。最后,我们将看看 `Collections` 类中提供的专用实现和通用算法。
11+
12+
《《《 [下一节](di-shi-zhang-ji-he/10.1-java-ji-he-kuang-jia-de-zhu-yao-jie-kou.md)
13+
《《《 [返回首页](../)
14+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# 12.1 使用集合方法
2+
3+
《《《 [返回首页](../../)
4+
《《《 [上一节](./)
5+
6+
## 使用集合方法
7+
8+
为了说明集合类的用法,我们来构造一个小例子。 你的作者永远都想组织起来; 让我们想象一下,我们最新的努力涉及编写我们自己的待办事项经理。我们首先定义一个表示任务的类,然后用子类表示不同类型的任务,例如编写代码或拨打电话。
9+
10+
以下是我们将要使用的任务的定义:
11+
12+
```java
13+
public abstract class Task implements Comparable<Task> {
14+
protected Task() {}
15+
public boolean equals(Object o) {
16+
if (o instanceof Task) {
17+
return toString().equals(o.toString());
18+
} else
19+
return false;
20+
}
21+
public int compareTo(Task t) {
22+
return toString().compareTo(t.toString());
23+
}
24+
public int hashCode() {
25+
return toString().hashCode();
26+
}
27+
public abstract String toString();
28+
}
29+
```
30+
31+
我们只需要四个任务操作:`equals``compareTo``hashCode``toString``Equality` 将用于测试集合是否包含给定任务,比较将被有序集合(如 `OrderedSet``OrderedMap`)使用,并且哈希代码将被基于哈希表(例如 `HashSet``HashMap`)的集合使用,并且每当我们显示集合的内容时,都会使用任务的字符串表示形式。前三种方法是根据 `toString` 方法定义的,它被声明为抽象的,所以它必须在 `Task` 的每个子类中定义。如果两个任务由同一个字符串表示,我们认为两个任务相等,任务的自然排序与其字符串排序相同。这保证了任务的自然顺序与平等一致,正如 `3.1` 节所讨论的 - 也就是说,当 `equals` 返回 `true` 时,`compareTo` 返回 `0`
32+
33+
我们为两类任务定义子类,编写一些代码并拨打电话:
34+
35+
```java
36+
public final class CodingTask extends Task {
37+
private final String spec;
38+
public CodingTask(String spec) {
39+
this.spec = spec;
40+
}
41+
public String getSpec() { return spec; }
42+
public String toString() { return "code " + spec; }
43+
}
44+
public final class PhoneTask extends Task {
45+
private final String name;
46+
private final String number;
47+
public PhoneTask(String name, String number) {
48+
this.name = name;
49+
this.number = number;
50+
}
51+
public String getName() { return name; }
52+
public String getNumber() { return number; }
53+
public String toString() { return "phone " + name; }
54+
}
55+
```
56+
57+
编码任务由字符串指定,电话任务由要调用的人员的姓名和号码指定。在每种情况下,我们都提供了类的构造函数,访问其字段的方法以及将其转换为字符串的方法。根据良好的实践,我们已经通过声明这些字段为 `final` 来使这两种任务不可变,并且我们已声明两个子类都是最终的,以便任何人稍后可以定义可变子类(参见“最小化可变性”/“最喜欢不变性“)由 `Joshua Bloch``Addison-Wesley` 撰写的 `Effective Java``4` 章)。
58+
59+
`toString` 方法将字符串“code”和每个电话任务的每个编码任务的前面加上字符串“phone”。由于第一个字母顺序是按照字母顺序排在第二位,并且由于任务按照 `toString` 返回的结果排序,编码任务在任务自然排序之前出现在电话任务之前,这符合我们的需求 - 毕竟我们是极客!
60+
61+
为了紧凑,电话任务的 `toString` 方法仅返回要呼叫的人员的姓名,而不是电话号码。我们假设我们从不会创建两个具有相同名称和不同号码的电话任务;如果我们这样做了,使用 `toString` 返回的结果来测试相等性是错误的。
62+
63+
我们也定义一个空的任务:
64+
65+
```java
66+
public class EmptyTask extends Task {
67+
public EmptyTask() {}
68+
public String toString() { return ""; }
69+
}
70+
```
71+
72+
`12-1`。 任务管理器的示例任务和任务集合
73+
74+
```java
75+
PhoneTask mikePhone = new PhoneTask("Mike", "987 6543");
76+
PhoneTask paulPhone = new PhoneTask("Paul", "123 4567");
77+
CodingTask databaseCode = new CodingTask("db");
78+
CodingTask interfaceCode = new CodingTask("gui");
79+
CodingTask logicCode = new CodingTask("logic");
80+
Collection<PhoneTask> phoneTasks = new ArrayList<PhoneTask>();
81+
Collection<CodingTask> codingTasks = new ArrayList<CodingTask>();
82+
Collection<Task> mondayTasks = new ArrayList<Task>();
83+
Collection<Task> tuesdayTasks = new ArrayList<Task>();
84+
Collections.addAll(phoneTasks, mikePhone, paulPhone);
85+
Collections.addAll(codingTasks, databaseCode, interfaceCode, logicCode);
86+
Collections.addAll(mondayTasks, logicCode, mikePhone);
87+
Collections.addAll(tuesdayTasks, databaseCode, interfaceCode, paulPhone);
88+
assert phoneTasks.toString().equals("[phone Mike, phone Paul]");
89+
assert codingTasks.toString().equals("[code db, code gui, code logic]");
90+
assert mondayTasks.toString().equals("[code logic, phone Mike]");
91+
assert tuesdayTasks.toString().equals("[code db, code gui, phone Paul]");
92+
```
93+
94+
由于空字符串在字符串上的自然排序中位于所有其他字符串之前,因此空任务在任务的自然排序中位于所有其他字符之前。当我们构造有序集合的范围视图时,这个任务会很有用(见 `13.2` 节)。
95+
96+
`12-1` 展示了我们如何定义一系列要执行的任务(即使在真实系统中,他们更可能从数据库中检索)。我们选择了 `ArrayList` 作为本例中使用的 `Collection` 的实现,但我们不打算利用列表的任何特殊属性;我们将 `ArrayList` 视为 `Collection` 的实现,仅此而已。作为检索过程的一部分,我们已经使用 `1.4` 节中介绍的方法 `Collections.addAll` 将这些任务组织到由列表表示的各种类别中。
97+
98+
现在我们可以使用 `Collection` 的方法来处理这些类别。我们在这里介绍的例子按照前面介绍的顺序使用了这些方法。
99+
100+
**添加元素**我们可以将新任务添加到计划中:
101+
102+
```java
103+
mondayTasks.add(new PhoneTask("Ruth", "567 1234"));
104+
assert mondayTasks.toString().equals(
105+
"[code logic, phone Mike, phone Ruth]");
106+
```
107+
108+
或者我们可以将时间表组合在一起:
109+
110+
```java
111+
Collection<Task> allTasks = new ArrayList<Task>(mondayTasks);
112+
allTasks.addAll(tuesdayTasks);
113+
assert allTasks.toString().equals(
114+
"[code logic, phone Mike, phone Ruth, code db, code gui, phone Paul]");
115+
```
116+
117+
**删除元素**任务完成后,我们可以从时间表中删除它:
118+
119+
```java
120+
boolean wasPresent = mondayTasks.remove(mikePhone);
121+
assert wasPresent;
122+
assert mondayTasks.toString().equals("[code logic, phone Ruth]");
123+
```
124+
125+
我们可以完全清除一个时间表,因为它的所有任务都已完成(是的,正确的):
126+
127+
```java
128+
mondayTasks.clear();
129+
assert mondayTasks.toString().equals("[]");
130+
```
131+
132+
删除方法还允许我们以各种方式组合整个集合。 例如,要查看电话以外的其他任务是否安排在星期二,我们可以编写:
133+
134+
```java
135+
Collection<Task> tuesdayNonphoneTasks = new ArrayList<Task>(tuesdayTasks);
136+
tuesdayNonphoneTasks.removeAll(phoneTasks);
137+
assert tuesdayNonphoneTasks.toString().equals("[code db, code gui]");
138+
```
139+
140+
或查看当天计划拨打哪些电话:
141+
142+
```java
143+
Collection<Task> phoneTuesdayTasks = new ArrayList<Task>(tuesdayTasks);
144+
phoneTuesdayTasks.retainAll(phoneTasks);
145+
assert phoneTuesdayTasks.toString().equals("[phone Paul]");
146+
```
147+
148+
最后一个例子可以通过不同的方式获得相同的结果:
149+
150+
```java
151+
Collection<PhoneTask> tuesdayPhoneTasks =
152+
new ArrayList<PhoneTask>(phoneTasks);
153+
tuesdayPhoneTasks.retainAll(tuesdayTasks);
154+
assert tuesdayPhoneTasks.toString().equals("[phone Paul]");
155+
```
156+
157+
请注意,`phoneTuesdayTasks` 具有 `List<Task>` 类型,而星期二 `PhoneTasks` 具有更精确的 `List<PhoneTask>` 类型。
158+
159+
这个例子提供了对这个组和下一个方法的签名的解释。我们已经讨论过(第 `2.6` 节),当添加到集合中的方法将它们的参数限制为它的参数类型时,他们为什么会接受 `Object``Collection<?>` 类型的参数。以 `retainAll` 为例,它的合同要求删除这个集合中不存在于参数集合中的那些元素。这没有理由限制论证集合可能包含的内容;在前面的示例中,它可以包含任何种类的任务的实例,而不仅仅是 `PhoneTask`。即使将参数限制为参数类型的超类型的集合也太狭窄了;我们希望可能的限制性最小的类型是 `Collection<?>。类似的推理适用于`remove``removeAll``contains``containsAll\`
160+
161+
**查询集合的内容**这些方法允许我们检查,例如,上述操作是否正常工作。我们将在这里使用断言来使系统检查我们的信念,即我们已经正确编程了以前的操作。例如,如果 `tuesdayPhoneTasks` 不包含 `paulPhone`,则第一条语句将抛出一个 `AssertionError`
162+
163+
```java
164+
assert tuesdayPhoneTasks.contains(paulPhone);
165+
assert tuesdayTasks.containsAll(tuesdayPhoneTasks);
166+
assert mondayTasks.isEmpty();
167+
assert mondayTasks.size() == 0;
168+
```
169+
170+
使集合内容可用于进一步处理此组中的方法为集合提供迭代器或将其转换为数组。
171+
172+
`11.1` 节展示了如何在 `Java 5` 中用 `foreach` 语句代替 `iterator` 的最简单和最常见的显式使用,`foreach` 语句隐式使用它们。 但是有一些 `foreach` 不能帮助迭代的用法; 如果要更改集合的结构而不遇到 `ConcurrentModificationException`,或者想要并行处理两个列表,则必须使用显式迭代器。 例如,假设我们决定在星期二没有时间进行电话任务。 使用 `foreach` 将它们从我们的任务列表中过滤出来可能会很诱人,但是这不会出于第 `11.1` 节中描述的原因:
173+
174+
```java
175+
// throws ConcurrentModificationException
176+
for (Task t : tuesdayTasks) {
177+
if (t instanceof PhoneTask) {
178+
tuesdayTasks.remove(t);
179+
}
180+
}
181+
```
182+
183+
如果仍然使用修改结构的 `Collection` 方法,则显式使用迭代器并不会有任何改进:
184+
185+
```java
186+
// throws ConcurrentModificationException
187+
for (Iterator<Task> it = tuesdayTasks.iterator() ; it.hasNext() ; ) {
188+
Task t = it.next();
189+
if (t instanceof PhoneTask) {
190+
tuesdayTasks.remove(t);
191+
}
192+
}
193+
```
194+
195+
但是使用迭代器的结构变化方法给出了我们想要的结果:
196+
197+
```java
198+
for (Iterator<Task> it = tuesdayTasks.iterator() ; it.hasNext() ; ) {
199+
Task t = it.next();
200+
if (t instanceof PhoneTask) {
201+
it.remove();
202+
}
203+
}
204+
```
205+
206+
`12-2`。 使用自然顺序合并集合
207+
208+
```java
209+
public class MergeCollections {
210+
static <T extends Comparable<? super T>> List<T> merge(Collection<? extends T> c1, Collection<? extends T> c2) {
211+
List<T> mergedList = new ArrayList<T>();
212+
Iterator<? extends T> itr1 = c1.iterator();
213+
Iterator<? extends T> itr2 = c2.iterator();
214+
T c1Element = getNextElement(itr1);
215+
T c2Element = getNextElement(itr2);
216+
// 每次迭代都会从迭代器中取一个任务;继续下去,直到迭代器都没有任何进一步的任务
217+
while (c1Element != null || c2Element != null) {
218+
//使用当前的c1元素,如果当前c2
219+
//元素为null,或者两者都是非空和c1元素
220+
//以自然顺序在c2元素之前
221+
boolean useC1Element = c2Element == null ||
222+
c1Element != null && c1Element.compareTo(c2Element) < 0;
223+
if (useC1Element) {
224+
mergedList.add(c1Element);
225+
c1Element = getNextElement(itr1);
226+
} else {
227+
mergedList.add(c2Element);
228+
c2Element = getNextElement(itr2);
229+
}
230+
}
231+
return mergedList;
232+
}
233+
static <E> E getNextElement(Iterator<E> itr) {
234+
if (itr.hasNext()){
235+
E nextElement = itr.next();
236+
if (nextElement == null) throw new NullPointerException();
237+
return nextElement;
238+
} else {
239+
return null;
240+
}
241+
}
242+
}
243+
```
244+
245+
再举一个例子,假设我们是挑剔的人,喜欢按照升序排列我们所有的任务列表,并且我们希望将两个任务列表合并到一个列表中,同时保持顺序。 例 `12-2` 展示了如何将两个集合合并到第三个集合中,前提是每个集合的迭代器按照自然顺序返回它们的元素。 此方法依赖于要合并的集合不包含空元素的事实; 如果遇到一个,该方法抛出一个 `NullPointerException`。碰巧,例 `12-1` 中的集合 `mondayTasks``tuesdayTasks` 都是按升序排列的,我们可以按如下方式合并它们:
246+
247+
```java
248+
Collection<Task> mergedTasks =
249+
MergeCollections.merge(mondayTasks, tuesdayTasks);
250+
assert mergedTasks.toString().equals(
251+
"[code db, code gui, code logic, phone Mike, phone Paul]");
252+
```
253+
254+
《《《 [下一节](12.2-ji-he-shi-xian.md)
255+
《《《 [返回首页](../../)
256+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# 12.2 集合实现
2+
3+
《《《 [返回首页](../../)
4+
《《《 [上一节](12.1-shi-yong-ji-he-fang-fa.md)
5+
6+
## 集合实现
7+
8+
`Collection` 没有具体的实现。 类 `AbstractCollection` 部分实现它,它是一系列骨架实现中的一个 - 包括 `AbstractSet``AbstractList`等 - 它们提供了每个接口的不同具体实现的通用功能。 这些框架实现可用于帮助框架接口的新实现的设计者。例如,`Collection` 可以作为包(无序列表)的接口,而实现包的程序员可以扩展 `AbstractCollection` 并查找大部分已经完成的实现工作。
9+
10+
《《《 [下一节](12.3-ji-he-gou-zao-han-shu.md)
11+
《《《 [返回首页](../../)
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# 12.3 集合构造函数
2+
3+
《《《 [返回首页](../../)
4+
《《《 [上一节](12.2-ji-he-shi-xian.md)
5+
6+
## 集合构造函数
7+
8+
我们将在接下来的三章中继续研究三种主要的集合,但我们首先应该解释大多数集合实现共享的两种常见的构造方法。 以 `HashSet` 为例,它们是:
9+
10+
```java
11+
public HashSet()
12+
public HashSet(Collection<? extends E> c)
13+
```
14+
15+
其中第一个创建一个空集,第二个集包含任何参数类型或其子类型集合的元素。使用这个构造函数与使用默认构造函数创建一个空集具有相同的效果,然后使用 `addAll` 添加集合的内容。这有时被称为“复制构造函数”,但该术语应该保留给构造函数,该构造函数复制同一类的对象,而第二种形式的构造函数可以接受任何实现接口 `Collection <? extends E>。`Joshua Bloch\` 建议使用术语“转换构造函数”。
16+
17+
并非所有集合类都具有这两种形式的构造函数 - 例如,`ArrayBlockingQueue` 不能在未修复其容量的情况下创建,并且 `SynchronousQueue` 根本不能容纳任何元素,因此不需要第二种形式的构造函数。另外,许多集合类除了这两个以外还有其他构造函数,但是它们所拥有的不是它们所实现的接口,而是取决于底层实现;这些额外的构造函数用于配置实现。
18+
19+
《《《 [下一节](../di-shi-san-zhang-sets/)
20+
《《《 [返回首页](../../)
21+

0 commit comments

Comments
 (0)