Skip to content

Commit 7df62d5

Browse files
committed
合并主仓库
2 parents 156035b + fd726b3 commit 7df62d5

File tree

2 files changed

+202
-10
lines changed

2 files changed

+202
-10
lines changed

docs/book/20-Generics.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
[TOC]
22

33
<!-- Generics -->
4+
45
# 第二十章 泛型
56

67
> 普通的类和方法只能使用特定的类型:基本数据类型或类类型。如果编写的代码需要应用于多种类型,这种严苛的限制对代码的束缚就会很大。
78
8-
多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。这样的方法更通用,应用范围更广。在类内部也是如此,在任何使用特定类型的地方,基类意味着更大的灵活性。除了`final`类(或只提供私有构造函数的类)任何类型都可被扩展,所以大部分时候这种灵活性是自带的。
9+
多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。这样的方法更通用,应用范围更广。在类内部也是如此,在任何使用特定类型的地方,基类意味着更大的灵活性。除了 `final` 类(或只提供私有构造函数的类)任何类型都可被扩展,所以大部分时候这种灵活性是自带的。
910

1011
拘泥于单一的继承体系太过局限,因为只有继承体系中的对象才能适用基类作为参数的方法中。如果方法以接口而不是类作为参数,限制就宽松多了,只要实现了接口就可以。这给予调用方一种选项,通过调整现有的类来实现接口,满足方法参数要求。接口可以突破继承体系的限制。
1112

@@ -23,16 +24,17 @@
2324

2425
Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如此,学习 Java 时基本不用参考 C++ 。
2526

26-
但是,Java 中的泛型需要与 C++ 进行对比,理由有两个:首先,理解 C++ *模板*(泛型的主要灵感来源,包括基本语法)的某些特性,有助于理解泛型的基础理念。同时,非常重要的一点是,你可以了解 Java 泛型的局限是什么,以及为什么会有这些局限。最终的目标是明确 Java 泛型的边界,让你成为一个程序高手。只有知道了某个技术不能做什么,你才能更好的做到所能做的(部分原因是,不必浪费时间在死胡同里)。
27+
但是,Java 中的泛型需要与 C++ 进行对比,理由有两个:首先,理解 C++ *模板*(泛型的主要灵感来源,包括基本语法)的某些特性,有助于理解泛型的基础理念。同时,非常重要的一点是,你可以了解 Java 泛型的局限是什么,以及为什么会有这些局限。最终的目标是明确 Java 泛型的边界,让你成为一个程序高手。只有知道了某个技术不能做什么,你才能更好地做到所能做的(部分原因是,不必浪费时间在死胡同里)。
2728

2829
第二个原因是,在 Java 社区中,大家普遍对 C++ 模板有一种误解,而这种误解可能会令你在理解泛型的意图时产生偏差。
2930

3031
因此,本章中会介绍少量 C++ 模板的例子,仅当它们确实可以加深理解时才会引入。
3132

3233
<!-- Simple Generics -->
34+
3335
## 简单泛型
3436

35-
促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./12-Collections.md)章节。集合用于存放要使用对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。
37+
促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./12-Collections.md)章节。集合用于存放要使用到的对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。
3638

3739
我们先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型:
3840

@@ -48,7 +50,7 @@ public class Holder1 {
4850
}
4951
```
5052

51-
这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到每个类型都编写一个新的类
53+
这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到的每个类型都编写一个新的类
5254

5355
在 Java 5 之前,我们可以让这个类直接持有 `Object` 类型的对象:
5456

@@ -116,15 +118,15 @@ public class Diamond<T> {
116118

117119
注意,在 `h3` 的定义处,`=` 右边的尖括号是空的(称为“钻石语法”),而不是重复左边的类型信息。在本书剩余部分都会使用这种语法。
118120

119-
一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定类型参数即可
121+
一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定它们的名称和类型参数列表即可
120122

121123
### 一个元组类库
122124

123-
有时一个方法需要能返回多个对象。而 **return** 语句只能返回单个对象,解决方法就是创建一个容器对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。但是有了泛型,我们就可以一劳永逸。同时,还获得了编译时的类型安全。
125+
有时一个方法需要能返回多个对象。而 **return** 语句只能返回单个对象,解决方法就是创建一个对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。但是有了泛型,我们就可以一劳永逸。同时,还获得了编译时的类型安全。
124126

125-
这个概念称为*元组*它是将一组对象直接打包存储于其中的一个单一对象。可以从容器对象读取其中的元素,但不允许向其中存储新对象(这个概念也称为 *数据传输对象**信使* )。
127+
这个概念称为*元组*它是将一组对象直接打包存储于单一对象中。可以从该对象读取其中的元素,但不允许向其中存储新对象(这个概念也称为 *数据传输对象**信使* )。
126128

127-
通常,元组可以具有任意长度,元组中的对象可以是不同类型的。不过,我们希望能够为每个对象指明其类型,并且从元组中读取出来时,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。下面是一个可以存储两个对象的元组:
129+
通常,元组可以具有任意长度,元组中的对象可以是不同类型的。不过,我们希望能够为每个对象指明类型,并且从元组中读取出来时,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。下面是一个可以存储两个对象的元组:
128130

129131
```java
130132
// onjava/Tuple2.java
@@ -359,6 +361,7 @@ brown over fox quick quick dog brown The brown lazy brown
359361
`RandomList` 继承了 `ArrayList` 的所有方法。本例中只添加了 `select()` 这个方法。
360362

361363
<!-- Generic Interfaces -->
364+
362365
## 泛型接口
363366

364367
泛型也可以应用于接口。例如 *生成器*,这是一种专门负责创建对象的类。实际上,这是 *工厂方法* 设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。生成器无需额外的信息就知道如何创建新对象。
@@ -520,7 +523,7 @@ public class Fibonacci implements Supplier<Integer> {
520523

521524
虽然我们在 `Fibonacci` 类的里里外外使用的都是 `int` 类型,但是其参数类型却是 `Integer`。这个例子引出了 Java 泛型的一个局限性:基本类型无法作为类型参数。不过 Java 5 具备自动装箱和拆箱的功能,可以很方便地在基本类型和相应的包装类之间进行转换。通过这个例子中 `Fibonacci` 类对 `int` 的使用,我们已经看到了这种效果。
522525

523-
如果还想更进一步,编写一个实现了 `Iterator``Fibnoacci` 生成器。我们的一个选择是重写这个类,令其实现 `Iterator` 接口。不过,你并不是总能拥有源代码的控制权,并且,除非必须这么做,否则,我们也不愿意重写一个类。而且我们还有另一种选择,就是创建一个 *适配器* (Adapter) 来实现所需的接口,我们在前面介绍过这个设计模式。
526+
如果还想更进一步,编写一个实现了 `Iterable``Fibnoacci` 生成器。我们的一个选择是重写这个类,令其实现 `Iterable` 接口。不过,你并不是总能拥有源代码的控制权,并且,除非必须这么做,否则,我们也不愿意重写一个类。而且我们还有另一种选择,就是创建一个 *适配器* (Adapter) 来实现所需的接口,我们在前面介绍过这个设计模式。
524527

525528
有多种方法可以实现适配器。例如,可以通过继承来创建适配器类:
526529

@@ -564,7 +567,7 @@ extends Fibonacci implements Iterable<Integer> {
564567
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
565568
```
566569

567-
在循环语句中使用 `IterableFibonacci`,必须在构造函数中提供一个边界值,这样 `hasNext()` 才知道何时返回 **false**,结束循环。
570+
*for-in* 语句中使用 `IterableFibonacci`,必须在构造函数中提供一个边界值,这样 `hasNext()` 才知道何时返回 **false**,结束循环。
568571

569572
<!-- Generic Methods -->
570573

docs/book/Appendix-Standard-IO.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,198 @@
33
<!-- Appendix: Standard I/O -->
44
# 附录:标准IO
55

6+
>*标准 I/O*这个术语参考Unix中的概念,指程序所使用的单一信息流(这种思想在大多数操作系统中,也有相似形式的实现)。
7+
8+
程序的所有输入都可以来自于*标准输入*,其所有输出都可以流向*标准输出*,并且其所有错误信息均可以发送到*标准错误**标准 I/O* 的意义在于程序之间可以很容易地连接起来,一个程序的标准输出可以作为另一个程序的标准输入。这是一个非常强大的工具。
9+
10+
## 从标准输入中读取
11+
12+
遵循标准 I/O 模型,Java 提供了标准输入流 `System.in`、标准输出流 `System.out` 和标准错误流 `System.err`。在本书中,你已经了解到如何使用 `System.out`将数据写到标准输出。 `System.out` 已经预先包装[^1]成了 `PrintStream` 对象。标准错误流 `System.err` 也预先包装为 `PrintStream` 对象,但是标准输入流 `System.in` 是原生的没有经过包装的 `InputStream`。这意味着尽管可以直接使用标准输出流 `System.in` 和标准错误流 `System.err`,但是在读取 `System.in` 之前必须先对其进行包装。
13+
14+
我们通常一次一行地读取输入。为了实现这个功能,将 `System.in` 包装成 `BufferedReader` 来使用,这要求我们用 `InputStreamReader``System.in` 转换[^2]`Reader` 。下面这个例子将键入的每一行显示出来:
15+
16+
```java
17+
// standardio/Echo.java
18+
// How to read from standard input
19+
import java.io.*;
20+
import onjava.TimedAbort;
21+
22+
public class Echo {
23+
public static void main(String[] args) {
24+
TimedAbort abort = new TimedAbort(2);
25+
new BufferedReader(
26+
new InputStreamReader(System.in))
27+
.lines()
28+
.peek(ln -> abort.restart())
29+
.forEach(System.out::println);
30+
// Ctrl-Z or two seconds inactivity
31+
// terminates the program
32+
}
33+
}
34+
```
35+
36+
`BufferedReader` 提供了 `lines()` 方法,返回类型是 `Stream<String>` 。这显示出流模型的的灵活性:仅使用标准输入就能很好地工作。 `peek()` 方法重启 `TimeAbort`,只要保证至少每隔两秒有输入就能够使程序保持开启状态。
37+
38+
## `System.out` 转换成 `PrintWriter`
39+
40+
`System.out` 是一个 `PrintStream`,而 `PrintStream` 是一个`OutputStream``PrintWriter` 有一个把 `OutputStream` 作为参数的构造器。因此,如果你需要的话,可以使用这个构造器把 `System.out` 转换成 `PrintWriter`
41+
42+
```java
43+
// standardio/ChangeSystemOut.java
44+
// Turn System.out into a PrintWriter
45+
46+
import java.io.*;
47+
48+
public class ChangeSystemOut {
49+
public static void main(String[] args) {
50+
PrintWriter out =
51+
new PrintWriter(System.out, true);
52+
out.println("Hello, world");
53+
}
54+
}
55+
```
56+
57+
输出结果:
58+
59+
```
60+
Hello, world
61+
```
62+
63+
要使用 `PrintWriter` 带有两个参数的构造器,并设置第二个参数为 `true`,从而使能自动刷新到输出缓冲区的功能;否则,可能无法看到打印输出。
64+
65+
## 重定向标准 I/O
66+
67+
Java的 `System` 类提供了简单的 `static` 方法调用,从而能够重定向标准输入流、标准输出流和标准错误流:
68+
- setIn(InputStream)
69+
- setOut(PrintStream)
70+
- setErr(PrintStream)
71+
72+
如果我们突然需要在显示器上创建大量的输出,而这些输出滚动的速度太快以至于无法阅读时,重定向输出就显得格外有用,可把输出内容重定向到文件中供后续查看。对于我们想重复测试特定的用户输入序列的命令行程序来说,重定向输入就很有价值。下例简单演示了这些方法的使用:
73+
74+
```java
75+
// standardio/Redirecting.java
76+
// Demonstrates standard I/O redirection
77+
import java.io.*;
78+
79+
public class Redirecting {
80+
public static void main(String[] args) {
81+
PrintStream console = System.out;
82+
try (
83+
BufferedInputStream in = new BufferedInputStream(
84+
new FileInputStream("Redirecting.java"));
85+
PrintStream out = new PrintStream(
86+
new BufferedOutputStream(
87+
new FileOutputStream("Redirecting.txt")))
88+
) {
89+
System.setIn(in);
90+
System.setOut(out);
91+
System.setErr(out);
92+
new BufferedReader(
93+
new InputStreamReader(System.in))
94+
.lines()
95+
.forEach(System.out::println);
96+
} catch (IOException e) {
97+
throw new RuntimeException(e);
98+
} finally {
99+
System.setOut(console);
100+
}
101+
}
102+
}
103+
```
104+
105+
该程序将文件中内容载入到标准输入,并把标准输出和标准错误重定向到另一个文件。它在程序的开始保存了最初对 `System.out` 对象的引用,并且在程序结束时将系统输出恢复到了该对象上。
106+
107+
I/O重定向操作的是字节流而不是字符流,因此使用 `InputStream``OutputStream`,而不是 `Reader``Writer`
108+
6109
<!-- Process Control -->
7110
## 执行控制
8111

112+
你经常需要在Java内部直接执行操作系统的程序,并控制这些程序的输入输出,Java类库提供了执行这些操作的类。
113+
114+
一项常见的任务是运行程序并将输出结果发送到控制台。本节包含了一个可以简化此任务的实用工具。
115+
116+
在使用这个工具时可能会产生两种类型的错误:导致异常的普通错误——对于这些错误我们只需要重新抛出一个 `RuntimeException` 即可,以及进程自身的执行过程中导致的错误——我们需要用单独的异常来报告这些错误:
117+
118+
```java
119+
// onjava/OSExecuteException.java
120+
package onjava;
121+
122+
public class OSExecuteException extends RuntimeException {
123+
public OSExecuteException(String why) {
124+
super(why);
125+
}
126+
}
127+
```
128+
129+
为了运行程序,我们需要传递给 `OSExecute.command()` 一个 `String command`,我们可以在控制台键入同样的指令运行程序。该命令传递给 `java.lang.ProcessBuilder` 的构造器(需要将其作为 `String` 对象的序列),然后启动生成的 `ProcessBuilder` 对象。
130+
131+
```java
132+
// onjava/OSExecute.java
133+
// Run an operating system command
134+
// and send the output to the console
135+
package onjava;
136+
import java.io.*;
137+
138+
public class OSExecute {
139+
public static void command(String command) {
140+
boolean err = false;
141+
try {
142+
Process process = new ProcessBuilder(
143+
command.split(" ")).start();
144+
try (
145+
BufferedReader results = new BufferedReader(
146+
new InputStreamReader(
147+
process.getInputStream()));
148+
BufferedReader errors = new BufferedReader(
149+
new InputStreamReader(
150+
process.getErrorStream()))
151+
) {
152+
results.lines()
153+
.forEach(System.out::println);
154+
err = errors.lines()
155+
.peek(System.err::println)
156+
.count() > 0;
157+
}
158+
} catch (IOException e) {
159+
throw new RuntimeException(e);
160+
}
161+
if (err)
162+
throw new OSExecuteException(
163+
"Errors executing " + command);
164+
}
165+
}
166+
```
167+
168+
为了捕获在程序执行时产生的标准输出流,我们可以调用 `getInputStream()`。这是因为 `InputStream` 是我们可以从中读取信息的流。
169+
170+
这里这些行只是被打印了出来,但是你也可以从 `command()` 捕获和返回它们。
171+
172+
该程序的错误被发送到了标准错误流,可以调用 `getErrorStream()` 捕获。如果存在任何错误,它们都会被打印并且抛出 `OSExcuteException` ,以便调用程序处理这个问题。
173+
174+
下面是展示如何使用 `OSExecute` 的示例:
175+
176+
```java
177+
// standardio/OSExecuteDemo.java
178+
// Demonstrates standard I/O redirection
179+
// {javap -cp build/classes/main OSExecuteDemo}
180+
import onjava.*;
181+
182+
public class OSExecuteDemo {}
183+
```
184+
185+
这里使用 `javap` 反编译器(随JDK发布)来反编译程序,编译结果:
186+
187+
```
188+
Compiled from "OSExecuteDemo.java"
189+
public class OSExecuteDemo {
190+
public OSExecuteDemo();
191+
}
192+
```
193+
194+
[^1]: 译者注:这里用到了**装饰器模式**
195+
196+
[^2]: 译者注:这里用到了**适配器模式**
197+
9198
<!-- 分页 -->
10199

11200
<div style="page-break-after: always;"></div>

0 commit comments

Comments
 (0)